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/document/XStorageBasedDocument.hpp>
23 #include <com/sun/star/embed/ElementModes.hpp>
24 #include <com/sun/star/embed/XTransactedObject.hpp>
25 #include <com/sun/star/frame/XModel.hpp>
26 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
27 #include <com/sun/star/script/ModuleType.hpp>
28 #include <com/sun/star/script/XLibraryContainer.hpp>
29 #include <com/sun/star/script/vba/XVBACompatibility.hpp>
30 #include <com/sun/star/script/vba/XVBAMacroResolver.hpp>
31 #include <com/sun/star/uno/XComponentContext.hpp>
32 #include <comphelper/configurationhelper.hxx>
33 #include <comphelper/string.hxx>
34 #include <rtl/tencinfo.h>
35 #include <rtl/ustrbuf.h>
36 #include "oox/helper/binaryinputstream.hxx"
37 #include "oox/helper/containerhelper.hxx"
38 #include "oox/helper/propertyset.hxx"
39 #include "oox/helper/textinputstream.hxx"
40 #include "oox/ole/olestorage.hxx"
41 #include "oox/ole/vbacontrol.hxx"
42 #include "oox/ole/vbahelper.hxx"
43 #include "oox/ole/vbainputstream.hxx"
44 #include "oox/ole/vbamodule.hxx"
45 #include "oox/token/properties.hxx"
50 // ============================================================================
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
;
64 // ============================================================================
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 // ----------------------------------------------------------------------------
86 VbaFilterConfig::VbaFilterConfig( const Reference
< XComponentContext
>& rxContext
, const OUString
& rConfigCompName
)
88 OSL_ENSURE( rxContext
.is(), "VbaFilterConfig::VbaFilterConfig - missing component context" );
89 if( rxContext
.is() ) try
91 OSL_ENSURE( !rConfigCompName
.isEmpty(), "VbaFilterConfig::VbaFilterConfig - invalid configuration component name" );
92 OUString aConfigPackage
= "org.openoffice.Office." + rConfigCompName
;
93 mxConfigAccess
= ConfigurationHelper::openConfig( rxContext
, aConfigPackage
, ConfigurationHelper::E_READONLY
);
95 catch(const Exception
& )
98 OSL_ENSURE( mxConfigAccess
.is(), "VbaFilterConfig::VbaFilterConfig - cannot open configuration" );
101 VbaFilterConfig::~VbaFilterConfig()
105 bool VbaFilterConfig::isImportVba() const
107 return lclReadConfigItem( mxConfigAccess
, "Load" );
110 bool VbaFilterConfig::isImportVbaExecutable() const
112 return lclReadConfigItem( mxConfigAccess
, "Executable" );
115 bool VbaFilterConfig::isExportVba() const
117 return lclReadConfigItem( mxConfigAccess
, "Save" );
120 // ============================================================================
122 VbaMacroAttacherBase::VbaMacroAttacherBase( const OUString
& rMacroName
) :
123 maMacroName( rMacroName
)
125 OSL_ENSURE( !maMacroName
.isEmpty(), "VbaMacroAttacherBase::VbaMacroAttacherBase - empty macro name" );
128 VbaMacroAttacherBase::~VbaMacroAttacherBase()
132 void VbaMacroAttacherBase::resolveAndAttachMacro( const Reference
< XVBAMacroResolver
>& rxResolver
)
136 attachMacro( rxResolver
->resolveVBAMacroToScriptURL( maMacroName
) );
138 catch(const Exception
& )
143 // ============================================================================
145 VbaProject::VbaProject( const Reference
< XComponentContext
>& rxContext
,
146 const Reference
< XModel
>& rxDocModel
, const OUString
& rConfigCompName
) :
147 VbaFilterConfig( rxContext
, rConfigCompName
),
148 mxContext( rxContext
),
149 mxDocModel( rxDocModel
),
150 maPrjName( "Standard" )
152 OSL_ENSURE( mxContext
.is(), "VbaProject::VbaProject - missing component context" );
153 OSL_ENSURE( mxDocModel
.is(), "VbaProject::VbaProject - missing document model" );
156 VbaProject::~VbaProject()
161 bool VbaProject::importVbaProject( StorageBase
& rVbaPrjStrg
)
163 // create GraphicHelper
164 Reference
< ::com::sun::star::frame::XFrame
> xFrame
;
165 if ( mxDocModel
.is() )
167 Reference
< ::com::sun::star::frame::XController
> xController
= mxDocModel
->getCurrentController();
168 xFrame
= xController
.is() ? xController
->getFrame() : NULL
;
170 StorageRef noStorage
;
171 // if the GraphicHelper tries to use noStorage it will of course crash
172 // but.. this shouldn't happen as there is no reason for GraphicHelper
173 // to do that when importing VBA projects
174 GraphicHelper
grfHlp( mxContext
, xFrame
, noStorage
);
175 importVbaProject( rVbaPrjStrg
, grfHlp
);
176 // return true if something has been imported
177 return hasModules() || hasDialogs();
180 void VbaProject::importVbaProject( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
, bool bDefaultColorBgr
)
182 if( rVbaPrjStrg
.isStorage() )
184 // load the code modules and forms
186 importVba( rVbaPrjStrg
, rGraphicHelper
, bDefaultColorBgr
);
187 // copy entire storage into model
189 copyStorage( rVbaPrjStrg
);
193 void VbaProject::registerMacroAttacher( const VbaMacroAttacherRef
& rxAttacher
)
195 OSL_ENSURE( rxAttacher
.get(), "VbaProject::registerMacroAttacher - unexpected empty reference" );
196 maMacroAttachers
.push_back( rxAttacher
);
199 bool VbaProject::hasModules() const
201 return mxBasicLib
.is() && mxBasicLib
->hasElements();
204 bool VbaProject::hasDialogs() const
206 return mxDialogLib
.is() && mxDialogLib
->hasElements();
209 // protected ------------------------------------------------------------------
211 void VbaProject::addDummyModule( const OUString
& rName
, sal_Int32 nType
)
213 OSL_ENSURE( !rName
.isEmpty(), "VbaProject::addDummyModule - missing module name" );
214 maDummyModules
[ rName
] = nType
;
217 void VbaProject::prepareImport()
221 void VbaProject::finalizeImport()
225 // private --------------------------------------------------------------------
227 Reference
< XLibraryContainer
> VbaProject::getLibraryContainer( sal_Int32 nPropId
)
229 PropertySet
aDocProp( mxDocModel
);
230 Reference
< XLibraryContainer
> xLibContainer( aDocProp
.getAnyProperty( nPropId
), UNO_QUERY
);
231 return xLibContainer
;
234 Reference
< XNameContainer
> VbaProject::openLibrary( sal_Int32 nPropId
, bool bCreateMissing
)
236 Reference
< XNameContainer
> xLibrary
;
239 Reference
< XLibraryContainer
> xLibContainer( getLibraryContainer( nPropId
), UNO_SET_THROW
);
240 if( bCreateMissing
&& !xLibContainer
->hasByName( maPrjName
) )
241 xLibContainer
->createLibrary( maPrjName
);
242 xLibrary
.set( xLibContainer
->getByName( maPrjName
), UNO_QUERY_THROW
);
244 catch(const Exception
& )
247 OSL_ENSURE( !bCreateMissing
|| xLibrary
.is(), "VbaProject::openLibrary - cannot create library" );
251 Reference
< XNameContainer
> VbaProject::createBasicLibrary()
253 if( !mxBasicLib
.is() )
254 mxBasicLib
= openLibrary( PROP_BasicLibraries
, true );
258 Reference
< XNameContainer
> VbaProject::createDialogLibrary()
260 if( !mxDialogLib
.is() )
261 mxDialogLib
= openLibrary( PROP_DialogLibraries
, true );
265 void VbaProject::importVba( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
, bool bDefaultColorBgr
)
267 readVbaModules( rVbaPrjStrg
);
268 importModulesAndForms(rVbaPrjStrg
, rGraphicHelper
, bDefaultColorBgr
);
271 void VbaProject::readVbaModules( StorageBase
& rVbaPrjStrg
)
273 StorageRef xVbaStrg
= rVbaPrjStrg
.openSubStorage( "VBA", false );
274 OSL_ENSURE( xVbaStrg
.get(), "VbaProject::readVbaModules - cannot open 'VBA' substorage" );
278 /* Read the 'VBA/dir' stream which contains general settings of the VBA
279 project such as the text encoding used throughout several streams, and
280 a list of all code modules.
282 BinaryXInputStream
aInStrm( xVbaStrg
->openInputStream( "dir" ), true );
283 // VbaInputStream implements decompression
284 VbaInputStream
aDirStrm( aInStrm
);
285 OSL_ENSURE( !aDirStrm
.isEof(), "VbaProject::importVba - cannot open 'dir' stream" );
286 if( aDirStrm
.isEof() )
289 // virtual call, derived classes may do some preparations
292 // read all records of the directory
293 rtl_TextEncoding eTextEnc
= RTL_TEXTENCODING_MS_1252
;
294 sal_uInt16 nModuleCount
= 0;
295 bool bExecutable
= isImportVbaExecutable();
298 sal_uInt16 nRecId
= 0;
299 StreamDataSequence aRecData
;
300 while( VbaHelper::readDirRecord( nRecId
, aRecData
, aDirStrm
) && (nRecId
!= VBA_ID_PROJECTEND
) )
302 // create record stream object from imported record data
303 SequenceInputStream
aRecStrm( aRecData
);
304 sal_Int32 nRecSize
= aRecData
.getLength();
307 #define OOX_ENSURE_RECORDSIZE( cond ) OSL_ENSURE( cond, "VbaProject::importVba - invalid record size" )
308 case VBA_ID_PROJECTCODEPAGE
:
310 OOX_ENSURE_RECORDSIZE( nRecSize
== 2 );
311 OSL_ENSURE( maModules
.empty(), "VbaProject::importVba - unexpected PROJECTCODEPAGE record" );
312 rtl_TextEncoding eNewTextEnc
= rtl_getTextEncodingFromWindowsCodePage( aRecStrm
.readuInt16() );
313 OSL_ENSURE( eNewTextEnc
!= RTL_TEXTENCODING_DONTKNOW
, "VbaProject::importVba - unknown text encoding" );
314 if( eNewTextEnc
!= RTL_TEXTENCODING_DONTKNOW
)
315 eTextEnc
= eNewTextEnc
;
318 case VBA_ID_PROJECTNAME
:
320 OUString aPrjName
= aRecStrm
.readCharArrayUC( nRecSize
, eTextEnc
);
321 OSL_ENSURE( !aPrjName
.isEmpty(), "VbaProject::importVba - invalid project name" );
322 if( !aPrjName
.isEmpty() )
323 maPrjName
= aPrjName
;
326 case VBA_ID_PROJECTMODULES
:
327 OOX_ENSURE_RECORDSIZE( nRecSize
== 2 );
328 OSL_ENSURE( maModules
.empty(), "VbaProject::importVba - unexpected PROJECTMODULES record" );
329 aRecStrm
>> nModuleCount
;
331 case VBA_ID_MODULENAME
:
333 OUString aName
= aRecStrm
.readCharArrayUC( nRecSize
, eTextEnc
);
334 OSL_ENSURE( !aName
.isEmpty(), "VbaProject::importVba - invalid module name" );
335 OSL_ENSURE( !maModules
.has( aName
), "VbaProject::importVba - multiple modules with the same name" );
336 VbaModuleMap::mapped_type
& rxModule
= maModules
[ aName
];
337 rxModule
.reset( new VbaModule( mxContext
, mxDocModel
, aName
, eTextEnc
, bExecutable
) );
338 // read all remaining records until the MODULEEND record
339 rxModule
->importDirRecords( aDirStrm
);
340 OSL_ENSURE( !maModulesByStrm
.has( rxModule
->getStreamName() ), "VbaProject::importVba - multiple modules with the same stream name" );
341 maModulesByStrm
[ rxModule
->getStreamName() ] = rxModule
;
344 #undef OOX_ENSURE_RECORDSIZE
347 OSL_ENSURE( nModuleCount
== maModules
.size(), "VbaProject::importVba - invalid module count" );
349 /* The directory does not contain the real type of the modules, it
350 distinguishes only between 'procedural' and 'document' (the latter
351 includes class and form modules). Now, the exact type of all modules
352 will be read from the 'PROJECT' stream. It consists of text lines in
353 'key=value' format which list the code modules by type.
355 - The line 'document=<modulename>/&HXXXXXXXX' declares document
356 modules. These are attached to the Word document (usually called
357 'ThisDocument'), the Excel workbook (usually called
358 'ThisWorkbook'), or single Excel worksheets or chartsheets (usually
359 called 'SheetX' or 'ChartX', X being a decimal number). Of course,
360 users may rename all these modules. The slash character separates
361 an automation server version number (hexadecimal 'XXXXXXXX') from
363 - The line 'Module=<modulename>' declares common procedural code
365 - The line 'Class=<modulename>' declares a class module.
366 - The line 'BaseClass=<modulename>' declares a code module attached
367 to a user form with the same name.
369 BinaryXInputStream
aPrjStrm( rVbaPrjStrg
.openInputStream( "PROJECT" ), true );
370 OSL_ENSURE( !aPrjStrm
.isEof(), "VbaProject::importVba - cannot open 'PROJECT' stream" );
371 // do not exit if this stream does not exist, but proceed to load the modules below
372 if( !aPrjStrm
.isEof() )
374 TextInputStream
aPrjTextStrm( mxContext
, aPrjStrm
, eTextEnc
);
375 OUString aKey
, aValue
;
376 bool bExitLoop
= false;
377 while( !bExitLoop
&& !aPrjTextStrm
.isEof() )
379 // read a text line from the stream
380 OUString aLine
= aPrjTextStrm
.readLine().trim();
381 sal_Int32 nLineLen
= aLine
.getLength();
382 // exit if a subsection starts (section name is given in brackets)
383 bExitLoop
= (nLineLen
>= 2) && (aLine
[ 0 ] == '[') && (aLine
[ nLineLen
- 1 ] == ']');
384 if( !bExitLoop
&& VbaHelper::extractKeyValue( aKey
, aValue
, aLine
) )
386 sal_Int32 nType
= ModuleType::UNKNOWN
;
387 if( aKey
.equalsIgnoreAsciiCase( "Document" ) )
389 nType
= ModuleType::DOCUMENT
;
390 // strip automation server version from module names
391 sal_Int32 nSlashPos
= aValue
.indexOf( '/' );
393 aValue
= aValue
.copy( 0, nSlashPos
);
395 else if( aKey
.equalsIgnoreAsciiCase( "Module" ) )
396 nType
= ModuleType::NORMAL
;
397 else if( aKey
.equalsIgnoreAsciiCase( "Class" ) )
398 nType
= ModuleType::CLASS
;
399 else if( aKey
.equalsIgnoreAsciiCase( "BaseClass" ) )
400 nType
= ModuleType::FORM
;
402 if( (nType
!= ModuleType::UNKNOWN
) && !aValue
.isEmpty() )
404 OSL_ENSURE( maModules
.has( aValue
), "VbaProject::importVba - module not found" );
405 if( VbaModule
* pModule
= maModules
.get( aValue
).get() )
406 pModule
->setType( nType
);
411 if( !maModules
.empty() ) try
413 /* Set library container to VBA compatibility mode. This will create
414 the VBA Globals object and store it in the Basic manager of the
418 Reference
< XVBACompatibility
> xVBACompat( getLibraryContainer( PROP_BasicLibraries
), UNO_QUERY_THROW
);
419 xVBACompat
->setVBACompatibilityMode( sal_True
);
420 xVBACompat
->setProjectName( maPrjName
);
423 catch(const Exception
& )
427 catch(const Exception
& )
432 void VbaProject::importModulesAndForms( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
, bool bDefaultColorBgr
)
434 StorageRef xVbaStrg
= rVbaPrjStrg
.openSubStorage( "VBA", false );
435 OSL_ENSURE( xVbaStrg
.get(), "VbaProject::importModulesAndForms - cannot open 'VBA' substorage" );
438 rtl_TextEncoding eTextEnc
= RTL_TEXTENCODING_MS_1252
;
439 bool bExecutable
= isImportVbaExecutable();
441 // create empty dummy modules
442 VbaModuleMap aDummyModules
;
443 for( DummyModuleMap::iterator aIt
= maDummyModules
.begin(), aEnd
= maDummyModules
.end(); aIt
!= aEnd
; ++aIt
)
445 OSL_ENSURE( !maModules
.has( aIt
->first
) && !aDummyModules
.has( aIt
->first
), "VbaProject::importVba - multiple modules with the same name" );
446 VbaModuleMap::mapped_type
& rxModule
= aDummyModules
[ aIt
->first
];
447 rxModule
.reset( new VbaModule( mxContext
, mxDocModel
, aIt
->first
, eTextEnc
, bExecutable
) );
448 rxModule
->setType( aIt
->second
);
451 /* Now it is time to load the source code. All modules will be inserted
452 into the Basic library of the document specified by the 'maPrjName'
453 member. Do not create the Basic library, if there are no modules
455 if( !maModules
.empty() || !aDummyModules
.empty() ) try
457 // get the model factory and the basic library
458 Reference
< XMultiServiceFactory
> xModelFactory( mxDocModel
, UNO_QUERY_THROW
);
459 Reference
< XNameContainer
> xBasicLib( createBasicLibrary(), UNO_SET_THROW
);
461 // try to get access to document objects related to code modules
462 Reference
< XNameAccess
> xDocObjectNA
;
465 xDocObjectNA
.set( xModelFactory
->createInstance( "ooo.vba.VBAObjectModuleObjectProvider" ), UNO_QUERY
);
467 catch(const Exception
& )
469 // not all documents support this
474 // #TODO cater for mxOleOverridesSink, like I used to before
475 // call Basic source code import for each module, boost::[c]ref enforces pass-by-ref
476 maModules
.forEachMem( &VbaModule::createAndImportModule
,
477 ::boost::ref( *xVbaStrg
), ::boost::cref( xBasicLib
),
478 ::boost::cref( xDocObjectNA
) );
480 // create empty dummy modules
481 aDummyModules
.forEachMem( &VbaModule::createEmptyModule
,
482 ::boost::cref( xBasicLib
), ::boost::cref( xDocObjectNA
) );
485 catch(const Exception
& )
489 /* Load the forms. The file format specification requires that a module
490 must exist for every form. We are a bit more tolerant and scan the
491 project storage for all form substorages. This may 'repair' broken VBA
492 storages that misses to mention a module for an existing form. */
493 ::std::vector
< OUString
> aElements
;
494 rVbaPrjStrg
.getElementNames( aElements
);
495 for( ::std::vector
< OUString
>::iterator aIt
= aElements
.begin(), aEnd
= aElements
.end(); aIt
!= aEnd
; ++aIt
)
497 // try to open the element as storage
500 StorageRef xSubStrg
= rVbaPrjStrg
.openSubStorage( *aIt
, false );
501 if( xSubStrg
.get() ) try
503 // resolve module name from storage name (which equals the module stream name)
504 VbaModule
* pModule
= maModulesByStrm
.get( *aIt
).get();
505 OSL_ENSURE( pModule
&& (pModule
->getType() == ModuleType::FORM
),
506 "VbaProject::importVba - form substorage without form module" );
507 OUString aModuleName
;
509 aModuleName
= pModule
->getName();
511 // create and import the form
512 Reference
< XNameContainer
> xDialogLib( createDialogLibrary(), UNO_SET_THROW
);
513 VbaUserForm
aForm( mxContext
, mxDocModel
, rGraphicHelper
, bDefaultColorBgr
);
514 aForm
.importForm( xDialogLib
, *xSubStrg
, aModuleName
, eTextEnc
);
516 catch(const Exception
& )
522 // attach macros to registered objects
524 // virtual call, derived classes may do some more processing
528 void VbaProject::attachMacros()
530 if( !maMacroAttachers
.empty() && mxContext
.is() ) try
532 Reference
< XMultiComponentFactory
> xFactory( mxContext
->getServiceManager(), UNO_SET_THROW
);
533 Sequence
< Any
> aArgs( 2 );
534 aArgs
[ 0 ] <<= mxDocModel
;
535 aArgs
[ 1 ] <<= maPrjName
;
536 Reference
< XVBAMacroResolver
> xResolver( xFactory
->createInstanceWithArgumentsAndContext(
537 "com.sun.star.script.vba.VBAMacroResolver", aArgs
, mxContext
), UNO_QUERY_THROW
);
538 maMacroAttachers
.forEachMem( &VbaMacroAttacherBase::resolveAndAttachMacro
, ::boost::cref( xResolver
) );
540 catch(const Exception
& )
545 void VbaProject::copyStorage( StorageBase
& rVbaPrjStrg
)
547 if( mxContext
.is() ) try
549 Reference
< XStorageBasedDocument
> xStorageBasedDoc( mxDocModel
, UNO_QUERY_THROW
);
550 Reference
< XStorage
> xDocStorage( xStorageBasedDoc
->getDocumentStorage(), UNO_QUERY_THROW
);
552 const sal_Int32 nOpenMode
= ElementModes::SEEKABLE
| ElementModes::WRITE
| ElementModes::TRUNCATE
;
553 Reference
< XStream
> xDocStream( xDocStorage
->openStreamElement( "_MS_VBA_Macros", nOpenMode
), UNO_SET_THROW
);
554 OleStorage
aDestStorage( mxContext
, xDocStream
, false );
555 rVbaPrjStrg
.copyStorageToStorage( aDestStorage
);
556 aDestStorage
.commit();
558 Reference
< XTransactedObject
>( xDocStorage
, UNO_QUERY_THROW
)->commit();
560 catch(const Exception
& )
565 // ============================================================================
570 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */