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 <osl/diagnose.h>
35 #include <rtl/tencinfo.h>
36 #include <rtl/ustrbuf.h>
37 #include "oox/helper/binaryinputstream.hxx"
38 #include "oox/helper/containerhelper.hxx"
39 #include "oox/helper/propertyset.hxx"
40 #include "oox/helper/textinputstream.hxx"
41 #include "oox/ole/olestorage.hxx"
42 #include "oox/ole/vbacontrol.hxx"
43 #include "oox/ole/vbahelper.hxx"
44 #include "oox/ole/vbainputstream.hxx"
45 #include "oox/ole/vbamodule.hxx"
46 #include "oox/token/properties.hxx"
51 using namespace ::com::sun::star::container
;
52 using namespace ::com::sun::star::document
;
53 using namespace ::com::sun::star::embed
;
54 using namespace ::com::sun::star::frame
;
55 using namespace ::com::sun::star::io
;
56 using namespace ::com::sun::star::lang
;
57 using namespace ::com::sun::star::script
;
58 using namespace ::com::sun::star::script::vba
;
59 using namespace ::com::sun::star::uno
;
61 using ::comphelper::ConfigurationHelper
;
65 bool lclReadConfigItem( const Reference
< XInterface
>& rxConfigAccess
, const OUString
& rItemName
)
67 // some applications do not support all configuration items, assume 'false' in this case
70 Any aItem
= ConfigurationHelper::readRelativeKey( rxConfigAccess
, "Filter/Import/VBA", rItemName
);
71 return aItem
.has
< bool >() && aItem
.get
< bool >();
73 catch(const Exception
& )
81 VbaFilterConfig::VbaFilterConfig( const Reference
< XComponentContext
>& rxContext
, const OUString
& rConfigCompName
)
83 OSL_ENSURE( rxContext
.is(), "VbaFilterConfig::VbaFilterConfig - missing component context" );
84 if( rxContext
.is() ) try
86 OSL_ENSURE( !rConfigCompName
.isEmpty(), "VbaFilterConfig::VbaFilterConfig - invalid configuration component name" );
87 OUString aConfigPackage
= "org.openoffice.Office." + rConfigCompName
;
88 mxConfigAccess
= ConfigurationHelper::openConfig( rxContext
, aConfigPackage
, ConfigurationHelper::E_READONLY
);
90 catch(const Exception
& )
93 OSL_ENSURE( mxConfigAccess
.is(), "VbaFilterConfig::VbaFilterConfig - cannot open configuration" );
96 VbaFilterConfig::~VbaFilterConfig()
100 bool VbaFilterConfig::isImportVba() const
102 return lclReadConfigItem( mxConfigAccess
, "Load" );
105 bool VbaFilterConfig::isImportVbaExecutable() const
107 return lclReadConfigItem( mxConfigAccess
, "Executable" );
110 bool VbaFilterConfig::isExportVba() const
112 return lclReadConfigItem( mxConfigAccess
, "Save" );
115 VbaMacroAttacherBase::VbaMacroAttacherBase( const OUString
& rMacroName
) :
116 maMacroName( rMacroName
)
118 OSL_ENSURE( !maMacroName
.isEmpty(), "VbaMacroAttacherBase::VbaMacroAttacherBase - empty macro name" );
121 VbaMacroAttacherBase::~VbaMacroAttacherBase()
125 void VbaMacroAttacherBase::resolveAndAttachMacro( const Reference
< XVBAMacroResolver
>& rxResolver
)
129 attachMacro( rxResolver
->resolveVBAMacroToScriptURL( maMacroName
) );
131 catch(const Exception
& )
136 VbaProject::VbaProject( const Reference
< XComponentContext
>& rxContext
,
137 const Reference
< XModel
>& rxDocModel
, const OUString
& rConfigCompName
) :
138 VbaFilterConfig( rxContext
, rConfigCompName
),
139 mxContext( rxContext
),
140 mxDocModel( rxDocModel
),
141 maPrjName( "Standard" )
143 OSL_ENSURE( mxContext
.is(), "VbaProject::VbaProject - missing component context" );
144 OSL_ENSURE( mxDocModel
.is(), "VbaProject::VbaProject - missing document model" );
147 VbaProject::~VbaProject()
151 bool VbaProject::importVbaProject( StorageBase
& rVbaPrjStrg
)
153 // create GraphicHelper
154 Reference
< ::com::sun::star::frame::XFrame
> xFrame
;
155 if ( mxDocModel
.is() )
157 Reference
< ::com::sun::star::frame::XController
> xController
= mxDocModel
->getCurrentController();
158 xFrame
= xController
.is() ? xController
->getFrame() : NULL
;
160 StorageRef noStorage
;
161 // if the GraphicHelper tries to use noStorage it will of course crash
162 // but.. this shouldn't happen as there is no reason for GraphicHelper
163 // to do that when importing VBA projects
164 GraphicHelper
grfHlp( mxContext
, xFrame
, noStorage
);
165 importVbaProject( rVbaPrjStrg
, grfHlp
);
166 // return true if something has been imported
167 return hasModules() || hasDialogs();
170 void VbaProject::importVbaProject( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
, bool bDefaultColorBgr
)
172 if( rVbaPrjStrg
.isStorage() )
174 // load the code modules and forms
176 importVba( rVbaPrjStrg
, rGraphicHelper
, bDefaultColorBgr
);
177 // copy entire storage into model
179 copyStorage( rVbaPrjStrg
);
183 void VbaProject::registerMacroAttacher( const VbaMacroAttacherRef
& rxAttacher
)
185 OSL_ENSURE( rxAttacher
.get(), "VbaProject::registerMacroAttacher - unexpected empty reference" );
186 maMacroAttachers
.push_back( rxAttacher
);
189 bool VbaProject::hasModules() const
191 return mxBasicLib
.is() && mxBasicLib
->hasElements();
194 bool VbaProject::hasDialogs() const
196 return mxDialogLib
.is() && mxDialogLib
->hasElements();
199 // protected ------------------------------------------------------------------
201 void VbaProject::addDummyModule( const OUString
& rName
, sal_Int32 nType
)
203 OSL_ENSURE( !rName
.isEmpty(), "VbaProject::addDummyModule - missing module name" );
204 maDummyModules
[ rName
] = nType
;
207 void VbaProject::prepareImport()
211 // private --------------------------------------------------------------------
213 Reference
< XLibraryContainer
> VbaProject::getLibraryContainer( sal_Int32 nPropId
)
215 PropertySet
aDocProp( mxDocModel
);
216 Reference
< XLibraryContainer
> xLibContainer( aDocProp
.getAnyProperty( nPropId
), UNO_QUERY
);
217 return xLibContainer
;
220 Reference
< XNameContainer
> VbaProject::openLibrary( sal_Int32 nPropId
, bool bCreateMissing
)
222 Reference
< XNameContainer
> xLibrary
;
225 Reference
< XLibraryContainer
> xLibContainer( getLibraryContainer( nPropId
), UNO_SET_THROW
);
226 if( bCreateMissing
&& !xLibContainer
->hasByName( maPrjName
) )
227 xLibContainer
->createLibrary( maPrjName
);
228 xLibrary
.set( xLibContainer
->getByName( maPrjName
), UNO_QUERY_THROW
);
230 catch(const Exception
& )
233 OSL_ENSURE( !bCreateMissing
|| xLibrary
.is(), "VbaProject::openLibrary - cannot create library" );
237 Reference
< XNameContainer
> VbaProject::createBasicLibrary()
239 if( !mxBasicLib
.is() )
240 mxBasicLib
= openLibrary( PROP_BasicLibraries
, true );
244 Reference
< XNameContainer
> VbaProject::createDialogLibrary()
246 if( !mxDialogLib
.is() )
247 mxDialogLib
= openLibrary( PROP_DialogLibraries
, true );
251 void VbaProject::importVba( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
, bool bDefaultColorBgr
)
253 readVbaModules( rVbaPrjStrg
);
254 importModulesAndForms(rVbaPrjStrg
, rGraphicHelper
, bDefaultColorBgr
);
257 void VbaProject::readVbaModules( StorageBase
& rVbaPrjStrg
)
259 StorageRef xVbaStrg
= rVbaPrjStrg
.openSubStorage( "VBA", false );
260 OSL_ENSURE( xVbaStrg
.get(), "VbaProject::readVbaModules - cannot open 'VBA' substorage" );
264 /* Read the 'VBA/dir' stream which contains general settings of the VBA
265 project such as the text encoding used throughout several streams, and
266 a list of all code modules.
268 BinaryXInputStream
aInStrm( xVbaStrg
->openInputStream( "dir" ), true );
269 // VbaInputStream implements decompression
270 VbaInputStream
aDirStrm( aInStrm
);
271 OSL_ENSURE( !aDirStrm
.isEof(), "VbaProject::importVba - cannot open 'dir' stream" );
272 if( aDirStrm
.isEof() )
275 // virtual call, derived classes may do some preparations
278 // read all records of the directory
279 rtl_TextEncoding eTextEnc
= RTL_TEXTENCODING_MS_1252
;
280 sal_uInt16 nModuleCount
= 0;
281 bool bExecutable
= isImportVbaExecutable();
283 sal_uInt16 nRecId
= 0;
284 StreamDataSequence aRecData
;
285 while( VbaHelper::readDirRecord( nRecId
, aRecData
, aDirStrm
) && (nRecId
!= VBA_ID_PROJECTEND
) )
287 // create record stream object from imported record data
288 SequenceInputStream
aRecStrm( aRecData
);
289 sal_Int32 nRecSize
= aRecData
.getLength();
292 case VBA_ID_PROJECTCODEPAGE
:
294 OSL_ENSURE( nRecSize
== 2, "VbaProject::importVba - invalid record size" );
295 OSL_ENSURE( maModules
.empty(), "VbaProject::importVba - unexpected PROJECTCODEPAGE record" );
296 rtl_TextEncoding eNewTextEnc
= rtl_getTextEncodingFromWindowsCodePage( aRecStrm
.readuInt16() );
297 OSL_ENSURE( eNewTextEnc
!= RTL_TEXTENCODING_DONTKNOW
, "VbaProject::importVba - unknown text encoding" );
298 if( eNewTextEnc
!= RTL_TEXTENCODING_DONTKNOW
)
299 eTextEnc
= eNewTextEnc
;
302 case VBA_ID_PROJECTNAME
:
304 OUString aPrjName
= aRecStrm
.readCharArrayUC( nRecSize
, eTextEnc
);
305 OSL_ENSURE( !aPrjName
.isEmpty(), "VbaProject::importVba - invalid project name" );
306 if( !aPrjName
.isEmpty() )
307 maPrjName
= aPrjName
;
310 case VBA_ID_PROJECTMODULES
:
311 OSL_ENSURE( nRecSize
== 2, "VbaProject::importVba - invalid record size" );
312 OSL_ENSURE( maModules
.empty(), "VbaProject::importVba - unexpected PROJECTMODULES record" );
313 nModuleCount
= aRecStrm
.readuInt16();
315 case VBA_ID_MODULENAME
:
317 OUString aName
= aRecStrm
.readCharArrayUC( nRecSize
, eTextEnc
);
318 OSL_ENSURE( !aName
.isEmpty(), "VbaProject::importVba - invalid module name" );
319 OSL_ENSURE( !maModules
.has( aName
), "VbaProject::importVba - multiple modules with the same name" );
320 VbaModuleMap::mapped_type
& rxModule
= maModules
[ aName
];
321 rxModule
.reset( new VbaModule( mxContext
, mxDocModel
, aName
, eTextEnc
, bExecutable
) );
322 // read all remaining records until the MODULEEND record
323 rxModule
->importDirRecords( aDirStrm
);
324 OSL_ENSURE( !maModulesByStrm
.has( rxModule
->getStreamName() ), "VbaProject::importVba - multiple modules with the same stream name" );
325 maModulesByStrm
[ rxModule
->getStreamName() ] = rxModule
;
330 SAL_WARN_IF( nModuleCount
!= maModules
.size(), "oox.ole", "VbaProject::importVba - invalid module count" );
332 /* The directory does not contain the real type of the modules, it
333 distinguishes only between 'procedural' and 'document' (the latter
334 includes class and form modules). Now, the exact type of all modules
335 will be read from the 'PROJECT' stream. It consists of text lines in
336 'key=value' format which list the code modules by type.
338 - The line 'document=<modulename>/&HXXXXXXXX' declares document
339 modules. These are attached to the Word document (usually called
340 'ThisDocument'), the Excel workbook (usually called
341 'ThisWorkbook'), or single Excel worksheets or chartsheets (usually
342 called 'SheetX' or 'ChartX', X being a decimal number). Of course,
343 users may rename all these modules. The slash character separates
344 an automation server version number (hexadecimal 'XXXXXXXX') from
346 - The line 'Module=<modulename>' declares common procedural code
348 - The line 'Class=<modulename>' declares a class module.
349 - The line 'BaseClass=<modulename>' declares a code module attached
350 to a user form with the same name.
352 BinaryXInputStream
aPrjStrm( rVbaPrjStrg
.openInputStream( "PROJECT" ), true );
353 OSL_ENSURE( !aPrjStrm
.isEof(), "VbaProject::importVba - cannot open 'PROJECT' stream" );
354 // do not exit if this stream does not exist, but proceed to load the modules below
355 if( !aPrjStrm
.isEof() )
357 TextInputStream
aPrjTextStrm( mxContext
, aPrjStrm
, eTextEnc
);
358 OUString aKey
, aValue
;
359 bool bExitLoop
= false;
360 while( !bExitLoop
&& !aPrjTextStrm
.isEof() )
362 // read a text line from the stream
363 OUString aLine
= aPrjTextStrm
.readLine().trim();
364 sal_Int32 nLineLen
= aLine
.getLength();
365 // exit if a subsection starts (section name is given in brackets)
366 bExitLoop
= (nLineLen
>= 2) && (aLine
[ 0 ] == '[') && (aLine
[ nLineLen
- 1 ] == ']');
367 if( !bExitLoop
&& VbaHelper::extractKeyValue( aKey
, aValue
, aLine
) )
369 sal_Int32 nType
= ModuleType::UNKNOWN
;
370 if( aKey
.equalsIgnoreAsciiCase( "Document" ) )
372 nType
= ModuleType::DOCUMENT
;
373 // strip automation server version from module names
374 sal_Int32 nSlashPos
= aValue
.indexOf( '/' );
376 aValue
= aValue
.copy( 0, nSlashPos
);
378 else if( aKey
.equalsIgnoreAsciiCase( "Module" ) )
379 nType
= ModuleType::NORMAL
;
380 else if( aKey
.equalsIgnoreAsciiCase( "Class" ) )
381 nType
= ModuleType::CLASS
;
382 else if( aKey
.equalsIgnoreAsciiCase( "BaseClass" ) )
383 nType
= ModuleType::FORM
;
385 if( (nType
!= ModuleType::UNKNOWN
) && !aValue
.isEmpty() )
387 OSL_ENSURE( maModules
.has( aValue
), "VbaProject::importVba - module not found" );
388 if( VbaModule
* pModule
= maModules
.get( aValue
).get() )
389 pModule
->setType( nType
);
394 if( !maModules
.empty() ) try
396 /* Set library container to VBA compatibility mode. This will create
397 the VBA Globals object and store it in the Basic manager of the
401 Reference
< XVBACompatibility
> xVBACompat( getLibraryContainer( PROP_BasicLibraries
), UNO_QUERY_THROW
);
402 xVBACompat
->setVBACompatibilityMode( sal_True
);
403 xVBACompat
->setProjectName( maPrjName
);
406 catch(const Exception
& )
410 catch(const Exception
& )
415 void VbaProject::importModulesAndForms( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
, bool bDefaultColorBgr
)
417 StorageRef xVbaStrg
= rVbaPrjStrg
.openSubStorage( "VBA", false );
418 OSL_ENSURE( xVbaStrg
.get(), "VbaProject::importModulesAndForms - cannot open 'VBA' substorage" );
421 rtl_TextEncoding eTextEnc
= RTL_TEXTENCODING_MS_1252
;
422 bool bExecutable
= isImportVbaExecutable();
424 // create empty dummy modules
425 VbaModuleMap aDummyModules
;
426 for( DummyModuleMap::iterator aIt
= maDummyModules
.begin(), aEnd
= maDummyModules
.end(); aIt
!= aEnd
; ++aIt
)
428 OSL_ENSURE( !maModules
.has( aIt
->first
) && !aDummyModules
.has( aIt
->first
), "VbaProject::importVba - multiple modules with the same name" );
429 VbaModuleMap::mapped_type
& rxModule
= aDummyModules
[ aIt
->first
];
430 rxModule
.reset( new VbaModule( mxContext
, mxDocModel
, aIt
->first
, eTextEnc
, bExecutable
) );
431 rxModule
->setType( aIt
->second
);
434 /* Now it is time to load the source code. All modules will be inserted
435 into the Basic library of the document specified by the 'maPrjName'
436 member. Do not create the Basic library, if there are no modules
438 if( !maModules
.empty() || !aDummyModules
.empty() ) try
440 // get the model factory and the basic library
441 Reference
< XMultiServiceFactory
> xModelFactory( mxDocModel
, UNO_QUERY_THROW
);
442 Reference
< XNameContainer
> xBasicLib( createBasicLibrary(), UNO_SET_THROW
);
444 // try to get access to document objects related to code modules
445 Reference
< XNameAccess
> xDocObjectNA
;
448 xDocObjectNA
.set( xModelFactory
->createInstance( "ooo.vba.VBAObjectModuleObjectProvider" ), UNO_QUERY
);
450 catch(const Exception
& )
452 // not all documents support this
457 // #TODO cater for mxOleOverridesSink, like I used to before
458 // call Basic source code import for each module, boost::[c]ref enforces pass-by-ref
459 maModules
.forEachMem( &VbaModule::createAndImportModule
,
460 ::boost::ref( *xVbaStrg
), ::boost::cref( xBasicLib
),
461 ::boost::cref( xDocObjectNA
) );
463 // create empty dummy modules
464 aDummyModules
.forEachMem( &VbaModule::createEmptyModule
,
465 ::boost::cref( xBasicLib
), ::boost::cref( xDocObjectNA
) );
468 catch(const Exception
& )
472 /* Load the forms. The file format specification requires that a module
473 must exist for every form. We are a bit more tolerant and scan the
474 project storage for all form substorages. This may 'repair' broken VBA
475 storages that misses to mention a module for an existing form. */
476 ::std::vector
< OUString
> aElements
;
477 rVbaPrjStrg
.getElementNames( aElements
);
478 for( ::std::vector
< OUString
>::iterator aIt
= aElements
.begin(), aEnd
= aElements
.end(); aIt
!= aEnd
; ++aIt
)
480 // try to open the element as storage
483 StorageRef xSubStrg
= rVbaPrjStrg
.openSubStorage( *aIt
, false );
484 if( xSubStrg
.get() ) try
486 // resolve module name from storage name (which equals the module stream name)
487 VbaModule
* pModule
= maModulesByStrm
.get( *aIt
).get();
488 OSL_ENSURE( pModule
&& (pModule
->getType() == ModuleType::FORM
),
489 "VbaProject::importVba - form substorage without form module" );
490 OUString aModuleName
;
492 aModuleName
= pModule
->getName();
494 // create and import the form
495 Reference
< XNameContainer
> xDialogLib( createDialogLibrary(), UNO_SET_THROW
);
496 VbaUserForm
aForm( mxContext
, mxDocModel
, rGraphicHelper
, bDefaultColorBgr
);
497 aForm
.importForm( xDialogLib
, *xSubStrg
, aModuleName
, eTextEnc
);
499 catch(const Exception
& )
505 // attach macros to registered objects
509 void VbaProject::attachMacros()
511 if( !maMacroAttachers
.empty() && mxContext
.is() ) try
513 Reference
< XMultiComponentFactory
> xFactory( mxContext
->getServiceManager(), UNO_SET_THROW
);
514 Sequence
< Any
> aArgs( 2 );
515 aArgs
[ 0 ] <<= mxDocModel
;
516 aArgs
[ 1 ] <<= maPrjName
;
517 Reference
< XVBAMacroResolver
> xResolver( xFactory
->createInstanceWithArgumentsAndContext(
518 "com.sun.star.script.vba.VBAMacroResolver", aArgs
, mxContext
), UNO_QUERY_THROW
);
519 maMacroAttachers
.forEachMem( &VbaMacroAttacherBase::resolveAndAttachMacro
, ::boost::cref( xResolver
) );
521 catch(const Exception
& )
526 void VbaProject::copyStorage( StorageBase
& rVbaPrjStrg
)
528 if( mxContext
.is() ) try
530 Reference
< XStorageBasedDocument
> xStorageBasedDoc( mxDocModel
, UNO_QUERY_THROW
);
531 Reference
< XStorage
> xDocStorage( xStorageBasedDoc
->getDocumentStorage(), UNO_QUERY_THROW
);
533 const sal_Int32 nOpenMode
= ElementModes::SEEKABLE
| ElementModes::WRITE
| ElementModes::TRUNCATE
;
534 Reference
< XStream
> xDocStream( xDocStorage
->openStreamElement( "_MS_VBA_Macros", nOpenMode
), UNO_SET_THROW
);
535 OleStorage
aDestStorage( mxContext
, xDocStream
, false );
536 rVbaPrjStrg
.copyStorageToStorage( aDestStorage
);
537 aDestStorage
.commit();
539 Reference
< XTransactedObject
>( xDocStorage
, UNO_QUERY_THROW
)->commit();
541 catch(const Exception
& )
549 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */