1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 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/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 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 . 18 */ 19 20 #include <oox/ole/vbamodule.hxx> 21 #include <com/sun/star/container/XNameContainer.hpp> 22 #include <com/sun/star/container/XIndexContainer.hpp> 23 #include <com/sun/star/script/ModuleInfo.hpp> 24 #include <com/sun/star/script/ModuleType.hpp> 25 #include <com/sun/star/script/vba/XVBAModuleInfo.hpp> 26 #include <com/sun/star/awt/KeyEvent.hpp> 27 #include <osl/diagnose.h> 28 #include <rtl/character.hxx> 29 #include <filter/msfilter/msvbahelper.hxx> 30 #include <oox/helper/binaryinputstream.hxx> 31 #include <oox/helper/storagebase.hxx> 32 #include <oox/helper/textinputstream.hxx> 33 #include <oox/ole/vbahelper.hxx> 34 #include <oox/ole/vbainputstream.hxx> 35 36 namespace oox { 37 namespace ole { 38 39 using namespace ::com::sun::star::lang; 40 using namespace ::com::sun::star::script::vba; 41 using namespace ::com::sun::star::uno; 42 using namespace ::com::sun::star; 43 44 using ::com::sun::star::awt::KeyEvent; 45 46 VbaModule::VbaModule( const Reference< XComponentContext >& rxContext, 47 const Reference< frame::XModel >& rxDocModel, 48 const OUString& rName, rtl_TextEncoding eTextEnc, bool bExecutable ) : 49 mxContext( rxContext ), 50 mxDocModel( rxDocModel ), 51 maName( rName ), 52 meTextEnc( eTextEnc ), 53 mnType( script::ModuleType::UNKNOWN ), 54 mnOffset( SAL_MAX_UINT32 ), 55 mbReadOnly( false ), 56 mbPrivate( false ), 57 mbExecutable( bExecutable ) 58 { 59 } 60 61 void VbaModule::importDirRecords( BinaryInputStream& rDirStrm ) 62 { 63 sal_uInt16 nRecId = 0; 64 StreamDataSequence aRecData; 65 while( VbaHelper::readDirRecord( nRecId, aRecData, rDirStrm ) && (nRecId != VBA_ID_MODULEEND) ) 66 { 67 SequenceInputStream aRecStrm( aRecData ); 68 sal_Int32 nRecSize = aRecData.getLength(); 69 switch( nRecId ) 70 { 71 #define OOX_ENSURE_RECORDSIZE( cond ) OSL_ENSURE( cond, "VbaModule::importDirRecords - invalid record size" ) 72 case VBA_ID_MODULENAME: 73 OSL_FAIL( "VbaModule::importDirRecords - unexpected MODULENAME record" ); 74 maName = aRecStrm.readCharArrayUC( nRecSize, meTextEnc ); 75 break; 76 case VBA_ID_MODULENAMEUNICODE: 77 break; 78 case VBA_ID_MODULESTREAMNAME: 79 maStreamName = aRecStrm.readCharArrayUC( nRecSize, meTextEnc ); 80 // Actually the stream name seems the best name to use 81 // the VBA_ID_MODULENAME name can sometimes be the wrong case 82 maName = maStreamName; 83 break; 84 case VBA_ID_MODULESTREAMNAMEUNICODE: 85 break; 86 case VBA_ID_MODULEDOCSTRING: 87 maDocString = aRecStrm.readCharArrayUC( nRecSize, meTextEnc ); 88 break; 89 case VBA_ID_MODULEDOCSTRINGUNICODE: 90 break; 91 case VBA_ID_MODULEOFFSET: 92 OOX_ENSURE_RECORDSIZE( nRecSize == 4 ); 93 mnOffset = aRecStrm.readuInt32(); 94 break; 95 case VBA_ID_MODULEHELPCONTEXT: 96 OOX_ENSURE_RECORDSIZE( nRecSize == 4 ); 97 break; 98 case VBA_ID_MODULECOOKIE: 99 OOX_ENSURE_RECORDSIZE( nRecSize == 2 ); 100 break; 101 case VBA_ID_MODULETYPEPROCEDURAL: 102 OOX_ENSURE_RECORDSIZE( nRecSize == 0 ); 103 OSL_ENSURE( mnType == script::ModuleType::UNKNOWN, "VbaModule::importDirRecords - multiple module type records" ); 104 mnType = script::ModuleType::NORMAL; 105 break; 106 case VBA_ID_MODULETYPEDOCUMENT: 107 OOX_ENSURE_RECORDSIZE( nRecSize == 0 ); 108 OSL_ENSURE( mnType == script::ModuleType::UNKNOWN, "VbaModule::importDirRecords - multiple module type records" ); 109 mnType = script::ModuleType::DOCUMENT; 110 break; 111 case VBA_ID_MODULEREADONLY: 112 OOX_ENSURE_RECORDSIZE( nRecSize == 0 ); 113 mbReadOnly = true; 114 break; 115 case VBA_ID_MODULEPRIVATE: 116 OOX_ENSURE_RECORDSIZE( nRecSize == 0 ); 117 mbPrivate = true; 118 break; 119 default: 120 OSL_FAIL( "VbaModule::importDirRecords - unknown module record" ); 121 #undef OOX_ENSURE_RECORDSIZE 122 } 123 } 124 OSL_ENSURE( !maName.isEmpty(), "VbaModule::importDirRecords - missing module name" ); 125 OSL_ENSURE( !maStreamName.isEmpty(), "VbaModule::importDirRecords - missing module stream name" ); 126 OSL_ENSURE( mnType != script::ModuleType::UNKNOWN, "VbaModule::importDirRecords - missing module type" ); 127 OSL_ENSURE( mnOffset < SAL_MAX_UINT32, "VbaModule::importDirRecords - missing module stream offset" ); 128 } 129 130 void VbaModule::createAndImportModule( StorageBase& rVbaStrg, 131 const Reference< container::XNameContainer >& rxBasicLib, 132 const Reference< container::XNameAccess >& rxDocObjectNA ) const 133 { 134 OUString aVBASourceCode = readSourceCode( rVbaStrg ); 135 createModule( aVBASourceCode, rxBasicLib, rxDocObjectNA ); 136 } 137 138 void VbaModule::createEmptyModule( const Reference< container::XNameContainer >& rxBasicLib, 139 const Reference< container::XNameAccess >& rxDocObjectNA ) const 140 { 141 createModule( OUString(), rxBasicLib, rxDocObjectNA ); 142 } 143 144 OUString VbaModule::readSourceCode( StorageBase& rVbaStrg ) const 145 { 146 OUStringBuffer aSourceCode; 147 static const char sUnmatchedRemovedTag[] = "Rem removed unmatched Sub/End: "; 148 if( !maStreamName.isEmpty() && (mnOffset != SAL_MAX_UINT32) ) 149 { 150 BinaryXInputStream aInStrm( rVbaStrg.openInputStream( maStreamName ), true ); 151 OSL_ENSURE( !aInStrm.isEof(), "VbaModule::readSourceCode - cannot open module stream" ); 152 // skip the 'performance cache' stored before the actual source code 153 aInStrm.seek( mnOffset ); 154 // if stream is still valid, load the source code 155 if( !aInStrm.isEof() ) 156 { 157 // decompression starts at current stream position of aInStrm 158 VbaInputStream aVbaStrm( aInStrm ); 159 // load the source code line-by-line, with some more processing 160 TextInputStream aVbaTextStrm( mxContext, aVbaStrm, meTextEnc ); 161 162 struct ProcedurePair 163 { 164 bool bInProcedure; 165 sal_uInt32 nPos; 166 ProcedurePair() : bInProcedure( false ), nPos( 0 ) {}; 167 } procInfo; 168 169 while( !aVbaTextStrm.isEof() ) 170 { 171 OUString aCodeLine = aVbaTextStrm.readLine(); 172 if( aCodeLine.match( "Attribute " ) ) 173 { 174 // attribute 175 int index = aCodeLine.indexOf( ".VB_ProcData.VB_Invoke_Func = " ); 176 if ( index != -1 ) 177 { 178 // format is 179 // 'Attribute Procedure.VB_ProcData.VB_Invoke_Func = "*\n14"' 180 // where 'Procedure' is the procedure name and '*' is the shortcut key 181 // note: his is only relevant for Excel, seems that 182 // word doesn't store the shortcut in the module 183 // attributes 184 int nSpaceIndex = aCodeLine.indexOf(' '); 185 OUString sProc = aCodeLine.copy( nSpaceIndex + 1, index - nSpaceIndex - 1); 186 // for Excel short cut key seems limited to cntrl+'a-z, A-Z' 187 OUString sKey = aCodeLine.copy( aCodeLine.lastIndexOf("= ") + 3, 1 ); 188 // only alpha key valid for key shortcut, however the api will accept other keys 189 if ( rtl::isAsciiAlpha( sKey[ 0 ] ) ) 190 { 191 // cntrl modifier is explicit ( but could be cntrl+shift ), parseKeyEvent 192 // will handle and uppercase letter appropriately 193 OUString sApiKey = "^"; 194 sApiKey += sKey; 195 try 196 { 197 KeyEvent aKeyEvent = ooo::vba::parseKeyEvent( sApiKey ); 198 ooo::vba::applyShortCutKeyBinding( mxDocModel, aKeyEvent, sProc ); 199 } 200 catch (const Exception&) 201 { 202 } 203 } 204 } 205 } 206 else 207 { 208 // Hack here to weed out any unmatched End Sub / Sub Foo statements. 209 // The behaviour of the vba ide practically guarantees the case and 210 // spacing of Sub statement(s). However, indentation can be arbitrary hence 211 // the trim. 212 OUString trimLine( aCodeLine.trim() ); 213 if ( mbExecutable && ( 214 trimLine.match("Sub ") || 215 trimLine.match("Public Sub ") || 216 trimLine.match("Private Sub ") || 217 trimLine.match("Static Sub ") ) ) 218 { 219 // this should never happen, basic doesn't support nested procedures 220 // first Sub Foo must be bogus 221 if ( procInfo.bInProcedure ) 222 { 223 // comment out the line 224 aSourceCode.insert( procInfo.nPos, sUnmatchedRemovedTag ); 225 // mark location of this Sub 226 procInfo.nPos = aSourceCode.getLength(); 227 } 228 else 229 { 230 procInfo.bInProcedure = true; 231 procInfo.nPos = aSourceCode.getLength(); 232 } 233 } 234 else if ( mbExecutable && aCodeLine.trim().match("End Sub") ) 235 { 236 // un-matched End Sub 237 if ( !procInfo.bInProcedure ) 238 { 239 aSourceCode.append( sUnmatchedRemovedTag ); 240 } 241 else 242 { 243 procInfo.bInProcedure = false; 244 procInfo.nPos = 0; 245 } 246 } 247 // normal source code line 248 if( !mbExecutable ) 249 aSourceCode.append( "Rem " ); 250 aSourceCode.append( aCodeLine ).append( '\n' ); 251 } 252 } 253 } 254 } 255 return aSourceCode.makeStringAndClear(); 256 } 257 258 void VbaModule::createModule( const OUString& rVBASourceCode, 259 const Reference< container::XNameContainer >& rxBasicLib, 260 const Reference< container::XNameAccess >& rxDocObjectNA ) const 261 { 262 if( maName.isEmpty() ) 263 return; 264 265 // prepare the Basic module 266 script::ModuleInfo aModuleInfo; 267 aModuleInfo.ModuleType = mnType; 268 OUStringBuffer aSourceCode; 269 aSourceCode.append( "Rem Attribute VBA_ModuleType=" ); 270 switch( mnType ) 271 { 272 case script::ModuleType::NORMAL: 273 aSourceCode.append( "VBAModule" ); 274 break; 275 case script::ModuleType::CLASS: 276 aSourceCode.append( "VBAClassModule" ); 277 break; 278 case script::ModuleType::FORM: 279 aSourceCode.append( "VBAFormModule" ); 280 // hack from old filter, document Basic should know the XModel, but it doesn't 281 aModuleInfo.ModuleObject.set( mxDocModel, UNO_QUERY ); 282 break; 283 case script::ModuleType::DOCUMENT: 284 aSourceCode.append( "VBADocumentModule" ); 285 // get the VBA implementation object associated to the document module 286 if( rxDocObjectNA.is() ) try 287 { 288 aModuleInfo.ModuleObject.set( rxDocObjectNA->getByName( maName ), UNO_QUERY ); 289 } 290 catch (const Exception&) 291 { 292 } 293 break; 294 default: 295 aSourceCode.append( "VBAUnknown" ); 296 } 297 aSourceCode.append( '\n' ); 298 if( mbExecutable ) 299 { 300 aSourceCode.append( "Option VBASupport 1\n" ); 301 if( mnType == script::ModuleType::CLASS ) 302 aSourceCode.append( "Option ClassModule\n" ); 303 } 304 else 305 { 306 // add a subroutine named after the module itself 307 aSourceCode.append( "Sub " ). 308 append( maName.replace( ' ', '_' ) ).append( '\n' ); 309 } 310 311 // append passed VBA source code 312 aSourceCode.append( rVBASourceCode ); 313 314 // close the subroutine named after the module 315 if( !mbExecutable ) 316 aSourceCode.append( "End Sub\n" ); 317 318 // insert extended module info 319 try 320 { 321 Reference< XVBAModuleInfo > xVBAModuleInfo( rxBasicLib, UNO_QUERY_THROW ); 322 xVBAModuleInfo->insertModuleInfo( maName, aModuleInfo ); 323 } 324 catch (const Exception&) 325 { 326 } 327 328 // insert the module into the passed Basic library 329 try 330 { 331 rxBasicLib->insertByName( maName, Any( aSourceCode.makeStringAndClear() ) ); 332 } 333 catch (const Exception&) 334 { 335 OSL_FAIL( "VbaModule::createModule - cannot insert module into library" ); 336 } 337 } 338 339 } // namespace ole 340 } // namespace oox 341 342 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 343
