Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / oox / source / ole / vbaproject.cxx
blobbf31f2a2350fd9ab7ded2db97a8780d65bf7f3d4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <rtl/ustrbuf.h>
39 #include <sal/log.hxx>
40 #include <oox/helper/binaryinputstream.hxx>
41 #include <oox/helper/containerhelper.hxx>
42 #include <oox/helper/propertyset.hxx>
43 #include <oox/helper/textinputstream.hxx>
44 #include <oox/ole/olestorage.hxx>
45 #include <oox/ole/vbacontrol.hxx>
46 #include <oox/ole/vbahelper.hxx>
47 #include <oox/ole/vbainputstream.hxx>
48 #include <oox/ole/vbamodule.hxx>
49 #include <oox/token/properties.hxx>
51 namespace oox {
52 namespace ole {
54 using namespace ::com::sun::star;
55 using namespace ::com::sun::star::container;
56 using namespace ::com::sun::star::document;
57 using namespace ::com::sun::star::embed;
58 using namespace ::com::sun::star::frame;
59 using namespace ::com::sun::star::io;
60 using namespace ::com::sun::star::lang;
61 using namespace ::com::sun::star::script;
62 using namespace ::com::sun::star::script::vba;
63 using namespace ::com::sun::star::uno;
65 using ::comphelper::ConfigurationHelper;
67 namespace {
69 bool lclReadConfigItem( const Reference< XInterface >& rxConfigAccess, const OUString& rItemName )
71 // some applications do not support all configuration items, assume 'false' in this case
72 try
74 Any aItem = ConfigurationHelper::readRelativeKey( rxConfigAccess, "Filter/Import/VBA", rItemName );
75 return aItem.has< bool >() && aItem.get< bool >();
77 catch(const Exception& )
80 return false;
83 } // namespace
85 VbaFilterConfig::VbaFilterConfig( const Reference< XComponentContext >& rxContext, const OUString& rConfigCompName )
87 OSL_ENSURE( rxContext.is(), "VbaFilterConfig::VbaFilterConfig - missing component context" );
88 if( rxContext.is() ) try
90 OSL_ENSURE( !rConfigCompName.isEmpty(), "VbaFilterConfig::VbaFilterConfig - invalid configuration component name" );
91 OUString aConfigPackage = "org.openoffice.Office." + rConfigCompName;
92 mxConfigAccess = ConfigurationHelper::openConfig( rxContext, aConfigPackage, comphelper::EConfigurationModes::ReadOnly );
94 catch(const Exception& )
97 OSL_ENSURE( mxConfigAccess.is(), "VbaFilterConfig::VbaFilterConfig - cannot open configuration" );
100 VbaFilterConfig::~VbaFilterConfig()
104 bool VbaFilterConfig::isImportVba() const
106 return lclReadConfigItem( mxConfigAccess, "Load" );
109 bool VbaFilterConfig::isImportVbaExecutable() const
111 return lclReadConfigItem( mxConfigAccess, "Executable" );
114 bool VbaFilterConfig::isExportVba() const
116 return lclReadConfigItem( mxConfigAccess, "Save" );
119 VbaMacroAttacherBase::VbaMacroAttacherBase( const OUString& rMacroName ) :
120 maMacroName( rMacroName )
122 OSL_ENSURE( !maMacroName.isEmpty(), "VbaMacroAttacherBase::VbaMacroAttacherBase - empty macro name" );
125 VbaMacroAttacherBase::~VbaMacroAttacherBase()
129 void VbaMacroAttacherBase::resolveAndAttachMacro( const Reference< XVBAMacroResolver >& rxResolver )
133 attachMacro( rxResolver->resolveVBAMacroToScriptURL( maMacroName ) );
135 catch(const Exception& )
140 VbaProject::VbaProject( const Reference< XComponentContext >& rxContext,
141 const Reference< XModel >& rxDocModel, const OUString& rConfigCompName ) :
142 VbaFilterConfig( rxContext, rConfigCompName ),
143 mxContext( rxContext ),
144 mxDocModel( rxDocModel ),
145 maPrjName( "Standard" )
147 OSL_ENSURE( mxContext.is(), "VbaProject::VbaProject - missing component context" );
148 OSL_ENSURE( mxDocModel.is(), "VbaProject::VbaProject - missing document model" );
151 VbaProject::~VbaProject()
155 bool VbaProject::importVbaProject( StorageBase& rVbaPrjStrg )
157 // create GraphicHelper
158 Reference< css::frame::XFrame > xFrame;
159 if ( mxDocModel.is() )
161 Reference< css::frame::XController > xController = mxDocModel->getCurrentController();
162 xFrame = xController.is() ? xController->getFrame() : nullptr;
164 StorageRef noStorage;
165 // if the GraphicHelper tries to use noStorage it will of course crash
166 // but... this shouldn't happen as there is no reason for GraphicHelper
167 // to do that when importing VBA projects
168 GraphicHelper grfHlp( mxContext, xFrame, noStorage );
169 importVbaProject( rVbaPrjStrg, grfHlp );
170 // return true if something has been imported
171 return (mxBasicLib.is() && mxBasicLib->hasElements()) ||
172 (mxDialogLib.is() && mxDialogLib->hasElements());
175 void VbaProject::importVbaProject( StorageBase& rVbaPrjStrg, const GraphicHelper& rGraphicHelper )
177 if( rVbaPrjStrg.isStorage() )
179 // load the code modules and forms
180 if( isImportVba() )
181 importVba( rVbaPrjStrg, rGraphicHelper );
182 // copy entire storage into model
183 if( isExportVba() )
184 copyStorage( rVbaPrjStrg );
188 void VbaProject::importVbaData(const uno::Reference<io::XInputStream>& xInputStream)
190 uno::Reference<document::XStorageBasedDocument> xStorageBasedDoc(mxDocModel, uno::UNO_QUERY);
191 uno::Reference<embed::XStorage> xDocStorage = xStorageBasedDoc->getDocumentStorage();
193 const sal_Int32 nOpenMode = ElementModes::SEEKABLE | ElementModes::WRITE | ElementModes::TRUNCATE;
194 uno::Reference<io::XOutputStream> xDocStream(xDocStorage->openStreamElement("_MS_VBA_Macros_XML", nOpenMode), uno::UNO_QUERY);
195 comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xDocStream);
197 uno::Reference<embed::XTransactedObject>(xDocStorage, uno::UNO_QUERY_THROW)->commit();
200 void VbaProject::registerMacroAttacher( const VbaMacroAttacherRef& rxAttacher )
202 OSL_ENSURE( rxAttacher.get(), "VbaProject::registerMacroAttacher - unexpected empty reference" );
203 maMacroAttachers.push_back( rxAttacher );
206 // protected ------------------------------------------------------------------
208 void VbaProject::addDummyModule( const OUString& rName, sal_Int32 nType )
210 OSL_ENSURE( !rName.isEmpty(), "VbaProject::addDummyModule - missing module name" );
211 maDummyModules[ rName ] = nType;
214 void VbaProject::prepareImport()
218 // private --------------------------------------------------------------------
220 Reference< XLibraryContainer > VbaProject::getLibraryContainer( sal_Int32 nPropId )
222 PropertySet aDocProp( mxDocModel );
223 Reference< XLibraryContainer > xLibContainer( aDocProp.getAnyProperty( nPropId ), UNO_QUERY );
224 return xLibContainer;
227 Reference< XNameContainer > VbaProject::openLibrary( sal_Int32 nPropId )
229 Reference< XNameContainer > xLibrary;
232 Reference< XLibraryContainer > xLibContainer( getLibraryContainer( nPropId ), UNO_SET_THROW );
233 if( !xLibContainer->hasByName( maPrjName ) )
234 xLibContainer->createLibrary( maPrjName );
235 xLibrary.set( xLibContainer->getByName( maPrjName ), UNO_QUERY_THROW );
237 catch(const Exception& )
240 OSL_ENSURE( xLibrary.is(), "VbaProject::openLibrary - cannot create library" );
241 return xLibrary;
244 Reference< XNameContainer > const & VbaProject::createBasicLibrary()
246 if( !mxBasicLib.is() )
247 mxBasicLib = openLibrary( PROP_BasicLibraries );
248 return mxBasicLib;
251 Reference< XNameContainer > const & VbaProject::createDialogLibrary()
253 if( !mxDialogLib.is() )
254 mxDialogLib = openLibrary( PROP_DialogLibraries );
255 return mxDialogLib;
258 void VbaProject::importVba( StorageBase& rVbaPrjStrg, const GraphicHelper& rGraphicHelper )
260 readVbaModules( rVbaPrjStrg );
261 importModulesAndForms(rVbaPrjStrg, rGraphicHelper );
262 // attach macros to registered objects
263 attachMacros();
266 void VbaProject::readVbaModules( StorageBase& rVbaPrjStrg )
268 StorageRef xVbaStrg = rVbaPrjStrg.openSubStorage( "VBA", false );
269 OSL_ENSURE( xVbaStrg.get(), "VbaProject::readVbaModules - cannot open 'VBA' substorage" );
270 if( !xVbaStrg )
271 return;
273 /* Read the 'VBA/dir' stream which contains general settings of the VBA
274 project such as the text encoding used throughout several streams, and
275 a list of all code modules.
277 BinaryXInputStream aInStrm( xVbaStrg->openInputStream( "dir" ), true );
278 // VbaInputStream implements decompression
279 VbaInputStream aDirStrm( aInStrm );
280 OSL_ENSURE( !aDirStrm.isEof(), "VbaProject::importVba - cannot open 'dir' stream" );
281 if( aDirStrm.isEof() )
282 return;
284 // virtual call, derived classes may do some preparations
285 prepareImport();
287 // read all records of the directory
288 rtl_TextEncoding eTextEnc = RTL_TEXTENCODING_MS_1252;
289 sal_uInt16 nModuleCount = 0;
290 bool bExecutable = isImportVbaExecutable();
292 sal_uInt16 nRecId = 0;
293 StreamDataSequence aRecData;
294 while( VbaHelper::readDirRecord( nRecId, aRecData, aDirStrm ) && (nRecId != VBA_ID_PROJECTEND) )
296 // create record stream object from imported record data
297 SequenceInputStream aRecStrm( aRecData );
298 sal_Int32 nRecSize = aRecData.getLength();
299 switch( nRecId )
301 case VBA_ID_PROJECTCODEPAGE:
303 OSL_ENSURE( nRecSize == 2, "VbaProject::importVba - invalid record size" );
304 OSL_ENSURE( maModules.empty(), "VbaProject::importVba - unexpected PROJECTCODEPAGE record" );
305 rtl_TextEncoding eNewTextEnc = rtl_getTextEncodingFromWindowsCodePage( aRecStrm.readuInt16() );
306 OSL_ENSURE( eNewTextEnc != RTL_TEXTENCODING_DONTKNOW, "VbaProject::importVba - unknown text encoding" );
307 if( eNewTextEnc != RTL_TEXTENCODING_DONTKNOW )
308 eTextEnc = eNewTextEnc;
310 break;
311 case VBA_ID_PROJECTNAME:
313 OUString aPrjName = aRecStrm.readCharArrayUC( nRecSize, eTextEnc );
314 OSL_ENSURE( !aPrjName.isEmpty(), "VbaProject::importVba - invalid project name" );
315 if( !aPrjName.isEmpty() )
316 maPrjName = aPrjName;
318 break;
319 case VBA_ID_PROJECTMODULES:
320 OSL_ENSURE( nRecSize == 2, "VbaProject::importVba - invalid record size" );
321 OSL_ENSURE( maModules.empty(), "VbaProject::importVba - unexpected PROJECTMODULES record" );
322 nModuleCount = aRecStrm.readuInt16();
323 break;
324 case VBA_ID_MODULENAME:
326 OUString aName = aRecStrm.readCharArrayUC( nRecSize, eTextEnc );
327 OSL_ENSURE( !aName.isEmpty(), "VbaProject::importVba - invalid module name" );
328 OSL_ENSURE( !maModules.has( aName ), "VbaProject::importVba - multiple modules with the same name" );
329 VbaModuleMap::mapped_type& rxModule = maModules[ aName ];
330 rxModule.reset( new VbaModule( mxContext, mxDocModel, aName, eTextEnc, bExecutable ) );
331 // read all remaining records until the MODULEEND record
332 rxModule->importDirRecords( aDirStrm );
333 OSL_ENSURE( !maModulesByStrm.has( rxModule->getStreamName() ), "VbaProject::importVba - multiple modules with the same stream name" );
334 maModulesByStrm[ rxModule->getStreamName() ] = rxModule;
336 break;
339 SAL_WARN_IF( nModuleCount != maModules.size(), "oox", "VbaProject::importVba - invalid module count" );
341 /* The directory does not contain the real type of the modules, it
342 distinguishes only between 'procedural' and 'document' (the latter
343 includes class and form modules). Now, the exact type of all modules
344 will be read from the 'PROJECT' stream. It consists of text lines in
345 'key=value' format which list the code modules by type.
347 - The line 'document=<modulename>/&HXXXXXXXX' declares document
348 modules. These are attached to the Word document (usually called
349 'ThisDocument'), the Excel workbook (usually called
350 'ThisWorkbook'), or single Excel worksheets or chartsheets (usually
351 called 'SheetX' or 'ChartX', X being a decimal number). Of course,
352 users may rename all these modules. The slash character separates
353 an automation server version number (hexadecimal 'XXXXXXXX') from
354 the module name.
355 - The line 'Module=<modulename>' declares common procedural code
356 modules.
357 - The line 'Class=<modulename>' declares a class module.
358 - The line 'BaseClass=<modulename>' declares a code module attached
359 to a user form with the same name.
361 BinaryXInputStream aPrjStrm( rVbaPrjStrg.openInputStream( "PROJECT" ), true );
362 OSL_ENSURE( !aPrjStrm.isEof(), "VbaProject::importVba - cannot open 'PROJECT' stream" );
363 // do not exit if this stream does not exist, but proceed to load the modules below
364 if( !aPrjStrm.isEof() )
366 TextInputStream aPrjTextStrm( mxContext, aPrjStrm, eTextEnc );
367 OUString aKey, aValue;
368 bool bExitLoop = false;
369 while( !bExitLoop && !aPrjTextStrm.isEof() )
371 // read a text line from the stream
372 OUString aLine = aPrjTextStrm.readLine().trim();
373 sal_Int32 nLineLen = aLine.getLength();
374 // exit if a subsection starts (section name is given in brackets)
375 bExitLoop = (nLineLen >= 2) && (aLine[ 0 ] == '[') && (aLine[ nLineLen - 1 ] == ']');
376 if( !bExitLoop && VbaHelper::extractKeyValue( aKey, aValue, aLine ) )
378 sal_Int32 nType = ModuleType::UNKNOWN;
379 if( aKey.equalsIgnoreAsciiCase( "Document" ) )
381 nType = ModuleType::DOCUMENT;
382 // strip automation server version from module names
383 sal_Int32 nSlashPos = aValue.indexOf( '/' );
384 if( nSlashPos >= 0 )
385 aValue = aValue.copy( 0, nSlashPos );
387 else if( aKey.equalsIgnoreAsciiCase( "Module" ) )
388 nType = ModuleType::NORMAL;
389 else if( aKey.equalsIgnoreAsciiCase( "Class" ) )
390 nType = ModuleType::CLASS;
391 else if( aKey.equalsIgnoreAsciiCase( "BaseClass" ) )
392 nType = ModuleType::FORM;
394 if( (nType != ModuleType::UNKNOWN) && !aValue.isEmpty() )
396 OSL_ENSURE( maModules.has( aValue ), "VbaProject::importVba - module not found" );
397 if( VbaModule* pModule = maModules.get( aValue ).get() )
398 pModule->setType( nType );
403 if( !maModules.empty() ) try
405 /* Set library container to VBA compatibility mode. This will create
406 the VBA Globals object and store it in the Basic manager of the
407 document. */
410 Reference< XVBACompatibility > xVBACompat( getLibraryContainer( PROP_BasicLibraries ), UNO_QUERY_THROW );
411 xVBACompat->setVBACompatibilityMode( true );
412 xVBACompat->setProjectName( maPrjName );
415 catch(const Exception& )
419 catch(const Exception& )
424 void VbaProject::importModulesAndForms( StorageBase& rVbaPrjStrg, const GraphicHelper& rGraphicHelper )
426 StorageRef xVbaStrg = rVbaPrjStrg.openSubStorage( "VBA", false );
427 OSL_ENSURE( xVbaStrg.get(), "VbaProject::importModulesAndForms - cannot open 'VBA' substorage" );
428 if( !xVbaStrg )
429 return;
430 rtl_TextEncoding eTextEnc = RTL_TEXTENCODING_MS_1252;
431 bool bExecutable = isImportVbaExecutable();
433 // create empty dummy modules
434 VbaModuleMap aDummyModules;
435 for (auto const& dummyModule : maDummyModules)
437 OSL_ENSURE( !maModules.has( dummyModule.first ) && !aDummyModules.has( dummyModule.first ), "VbaProject::importVba - multiple modules with the same name" );
438 VbaModuleMap::mapped_type& rxModule = aDummyModules[ dummyModule.first ];
439 rxModule.reset( new VbaModule( mxContext, mxDocModel, dummyModule.first, eTextEnc, bExecutable ) );
440 rxModule->setType( dummyModule.second );
443 /* Now it is time to load the source code. All modules will be inserted
444 into the Basic library of the document specified by the 'maPrjName'
445 member. Do not create the Basic library, if there are no modules
446 specified. */
447 if( !maModules.empty() || !aDummyModules.empty() ) try
449 // get the model factory and the basic library
450 Reference< XMultiServiceFactory > xModelFactory( mxDocModel, UNO_QUERY_THROW );
451 Reference< XNameContainer > xBasicLib( createBasicLibrary(), UNO_SET_THROW );
453 // try to get access to document objects related to code modules
454 Reference< XNameAccess > xDocObjectNA;
457 xDocObjectNA.set( xModelFactory->createInstance( "ooo.vba.VBAObjectModuleObjectProvider" ), UNO_QUERY );
459 catch(const Exception& )
461 // not all documents support this
464 if( xBasicLib.is() )
466 // #TODO cater for mxOleOverridesSink, like I used to before
467 // call Basic source code import for each module, std::[c]ref enforces pass-by-ref
468 maModules.forEachMem( &VbaModule::createAndImportModule,
469 ::std::ref( *xVbaStrg ), ::std::cref( xBasicLib ),
470 ::std::cref( xDocObjectNA ) );
472 // create empty dummy modules
473 aDummyModules.forEachMem( &VbaModule::createEmptyModule,
474 ::std::cref( xBasicLib ), ::std::cref( xDocObjectNA ) );
477 catch(const Exception& )
481 /* Load the forms. The file format specification requires that a module
482 must exist for every form. We are a bit more tolerant and scan the
483 project storage for all form substorages. This may 'repair' broken VBA
484 storages that misses to mention a module for an existing form. */
485 ::std::vector< OUString > aElements;
486 rVbaPrjStrg.getElementNames( aElements );
487 for (auto const& elem : aElements)
489 // try to open the element as storage
490 if( elem != "VBA" )
492 StorageRef xSubStrg = rVbaPrjStrg.openSubStorage( elem, false );
493 if( xSubStrg.get() ) try
495 // resolve module name from storage name (which equals the module stream name)
496 VbaModule* pModule = maModulesByStrm.get( elem ).get();
497 OSL_ENSURE( pModule && (pModule->getType() == ModuleType::FORM),
498 "VbaProject::importVba - form substorage without form module" );
499 OUString aModuleName;
500 if( pModule )
501 aModuleName = pModule->getName();
503 // create and import the form
504 Reference< XNameContainer > xDialogLib( createDialogLibrary(), UNO_SET_THROW );
505 VbaUserForm aForm( mxContext, mxDocModel, rGraphicHelper, true/*bDefaultColorBgr*/ );
506 aForm.importForm( xDialogLib, *xSubStrg, aModuleName, eTextEnc );
508 catch(const Exception& )
515 void VbaProject::attachMacros()
517 if( !maMacroAttachers.empty() && mxContext.is() ) try
519 comphelper::DocumentInfo::notifyMacroEventRead(mxDocModel);
521 Reference< XMultiComponentFactory > xFactory( mxContext->getServiceManager(), UNO_SET_THROW );
522 Sequence< Any > aArgs( 2 );
523 aArgs[ 0 ] <<= mxDocModel;
524 aArgs[ 1 ] <<= maPrjName;
525 Reference< XVBAMacroResolver > xResolver( xFactory->createInstanceWithArgumentsAndContext(
526 "com.sun.star.script.vba.VBAMacroResolver", aArgs, mxContext ), UNO_QUERY_THROW );
527 maMacroAttachers.forEachMem( &VbaMacroAttacherBase::resolveAndAttachMacro, ::std::cref( xResolver ) );
530 catch(const Exception& )
535 void VbaProject::copyStorage( StorageBase& rVbaPrjStrg )
537 if( mxContext.is() ) try
539 Reference< XStorageBasedDocument > xStorageBasedDoc( mxDocModel, UNO_QUERY_THROW );
540 Reference< XStorage > xDocStorage( xStorageBasedDoc->getDocumentStorage(), UNO_SET_THROW );
542 const sal_Int32 nOpenMode = ElementModes::SEEKABLE | ElementModes::WRITE | ElementModes::TRUNCATE;
543 Reference< XStream > xDocStream( xDocStorage->openStreamElement( "_MS_VBA_Macros", nOpenMode ), UNO_SET_THROW );
544 OleStorage aDestStorage( mxContext, xDocStream, false );
545 rVbaPrjStrg.copyStorageToStorage( aDestStorage );
546 aDestStorage.commit();
548 Reference< XTransactedObject >( xDocStorage, UNO_QUERY_THROW )->commit();
550 catch(const Exception& )
555 } // namespace ole
556 } // namespace oox
558 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */