Line data Source code
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 <boost/unordered_map.hpp>
22 : #include <com/sun/star/container/XNameContainer.hpp>
23 : #include <com/sun/star/container/XIndexContainer.hpp>
24 : #include <com/sun/star/script/ModuleInfo.hpp>
25 : #include <com/sun/star/script/ModuleType.hpp>
26 : #include <com/sun/star/script/vba/XVBAModuleInfo.hpp>
27 : #include <com/sun/star/awt/KeyEvent.hpp>
28 : #include <cppuhelper/implbase1.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 : // ============================================================================
40 :
41 : using namespace ::com::sun::star::lang;
42 : using namespace ::com::sun::star::script::vba;
43 : using namespace ::com::sun::star::uno;
44 : using namespace ::com::sun::star;
45 :
46 : using ::rtl::OUString;
47 : using ::rtl::OUStringBuffer;
48 : using ::com::sun::star::awt::KeyEvent;
49 : // ============================================================================
50 : typedef ::cppu::WeakImplHelper1< container::XIndexContainer > OleIdToNameContainer_BASE;
51 : typedef boost::unordered_map< sal_Int32, rtl::OUString > ObjIdToName;
52 :
53 0 : class OleIdToNameContainer : public OleIdToNameContainer_BASE
54 : {
55 : ObjIdToName ObjIdToNameHash;
56 : ::osl::Mutex m_aMutex;
57 0 : bool hasByIndex( ::sal_Int32 Index )
58 : {
59 0 : ::osl::MutexGuard aGuard( m_aMutex );
60 0 : return ( ObjIdToNameHash.find( Index ) != ObjIdToNameHash.end() );
61 : }
62 : public:
63 : OleIdToNameContainer() {}
64 : // XIndexContainer Methods
65 0 : virtual void SAL_CALL insertByIndex( ::sal_Int32 Index, const Any& Element ) throw (IllegalArgumentException, IndexOutOfBoundsException, WrappedTargetException, RuntimeException)
66 : {
67 0 : ::osl::MutexGuard aGuard( m_aMutex );
68 0 : rtl::OUString sOleName;
69 0 : if ( !( Element >>= sOleName ) )
70 0 : throw IllegalArgumentException();
71 0 : ObjIdToNameHash[ Index ] = sOleName;
72 0 : }
73 0 : virtual void SAL_CALL removeByIndex( ::sal_Int32 Index ) throw (IndexOutOfBoundsException, WrappedTargetException, RuntimeException)
74 : {
75 0 : ::osl::MutexGuard aGuard( m_aMutex );
76 0 : if ( !hasByIndex( Index ) )
77 0 : throw IndexOutOfBoundsException();
78 0 : ObjIdToNameHash.erase( ObjIdToNameHash.find( Index ) );
79 0 : }
80 : // XIndexReplace Methods
81 0 : virtual void SAL_CALL replaceByIndex( ::sal_Int32 Index, const Any& Element ) throw (IllegalArgumentException, IndexOutOfBoundsException, WrappedTargetException, RuntimeException)
82 : {
83 0 : ::osl::MutexGuard aGuard( m_aMutex );
84 0 : if ( !hasByIndex( Index ) )
85 0 : throw IndexOutOfBoundsException();
86 0 : rtl::OUString sOleName;
87 0 : if ( !( Element >>= sOleName ) )
88 0 : throw IllegalArgumentException();
89 0 : ObjIdToNameHash[ Index ] = sOleName;
90 0 : }
91 : // XIndexAccess Methods
92 0 : virtual ::sal_Int32 SAL_CALL getCount( ) throw (RuntimeException)
93 : {
94 0 : ::osl::MutexGuard aGuard( m_aMutex );
95 0 : return ObjIdToNameHash.size();
96 : }
97 0 : virtual Any SAL_CALL getByIndex( ::sal_Int32 Index ) throw (IndexOutOfBoundsException, WrappedTargetException, RuntimeException)
98 : {
99 0 : ::osl::MutexGuard aGuard( m_aMutex );
100 0 : if ( !hasByIndex( Index ) )
101 0 : throw IndexOutOfBoundsException();
102 0 : return makeAny( ObjIdToNameHash[ Index ] );
103 : }
104 : // XElementAccess Methods
105 0 : virtual Type SAL_CALL getElementType( ) throw (RuntimeException)
106 : {
107 0 : return ::getCppuType( static_cast< const ::rtl::OUString* >( 0 ) );
108 : }
109 0 : virtual ::sal_Bool SAL_CALL hasElements( ) throw (RuntimeException)
110 : {
111 0 : ::osl::MutexGuard aGuard( m_aMutex );
112 0 : return ( getCount() > 0 );
113 : }
114 : };
115 :
116 : // ============================================================================
117 :
118 15 : VbaModule::VbaModule( const Reference< XComponentContext >& rxContext,
119 : const Reference< frame::XModel >& rxDocModel,
120 : const OUString& rName, rtl_TextEncoding eTextEnc, bool bExecutable ) :
121 : mxContext( rxContext ),
122 : mxDocModel( rxDocModel ),
123 : maName( rName ),
124 : meTextEnc( eTextEnc ),
125 : mnType( script::ModuleType::UNKNOWN ),
126 : mnOffset( SAL_MAX_UINT32 ),
127 : mbReadOnly( false ),
128 : mbPrivate( false ),
129 15 : mbExecutable( bExecutable )
130 : {
131 15 : }
132 :
133 15 : void VbaModule::importDirRecords( BinaryInputStream& rDirStrm )
134 : {
135 15 : sal_uInt16 nRecId = 0;
136 15 : StreamDataSequence aRecData;
137 155 : while( VbaHelper::readDirRecord( nRecId, aRecData, rDirStrm ) && (nRecId != VBA_ID_MODULEEND) )
138 : {
139 125 : SequenceInputStream aRecStrm( aRecData );
140 125 : sal_Int32 nRecSize = aRecData.getLength();
141 125 : switch( nRecId )
142 : {
143 : #define OOX_ENSURE_RECORDSIZE( cond ) OSL_ENSURE( cond, "VbaModule::importDirRecords - invalid record size" )
144 : case VBA_ID_MODULENAME:
145 : OSL_FAIL( "VbaModule::importDirRecords - unexpected MODULENAME record" );
146 0 : maName = aRecStrm.readCharArrayUC( nRecSize, meTextEnc );
147 0 : break;
148 : case VBA_ID_MODULENAMEUNICODE:
149 5 : break;
150 : case VBA_ID_MODULESTREAMNAME:
151 15 : maStreamName = aRecStrm.readCharArrayUC( nRecSize, meTextEnc );
152 : // Actually the stream name seems the best name to use
153 : // the VBA_ID_MODULENAME name can sometimes be the wrong case
154 15 : maName = maStreamName;
155 15 : break;
156 : case VBA_ID_MODULESTREAMNAMEUNICODE:
157 15 : break;
158 : case VBA_ID_MODULEDOCSTRING:
159 15 : maDocString = aRecStrm.readCharArrayUC( nRecSize, meTextEnc );
160 15 : break;
161 : case VBA_ID_MODULEDOCSTRINGUNICODE:
162 15 : break;
163 : case VBA_ID_MODULEOFFSET:
164 : OOX_ENSURE_RECORDSIZE( nRecSize == 4 );
165 15 : aRecStrm >> mnOffset;
166 15 : break;
167 : case VBA_ID_MODULEHELPCONTEXT:
168 : OOX_ENSURE_RECORDSIZE( nRecSize == 4 );
169 15 : break;
170 : case VBA_ID_MODULECOOKIE:
171 : OOX_ENSURE_RECORDSIZE( nRecSize == 2 );
172 15 : break;
173 : case VBA_ID_MODULETYPEPROCEDURAL:
174 : OOX_ENSURE_RECORDSIZE( nRecSize == 0 );
175 : OSL_ENSURE( mnType == script::ModuleType::UNKNOWN, "VbaModule::importDirRecords - multiple module type records" );
176 2 : mnType = script::ModuleType::NORMAL;
177 2 : break;
178 : case VBA_ID_MODULETYPEDOCUMENT:
179 : OOX_ENSURE_RECORDSIZE( nRecSize == 0 );
180 : OSL_ENSURE( mnType == script::ModuleType::UNKNOWN, "VbaModule::importDirRecords - multiple module type records" );
181 13 : mnType = script::ModuleType::DOCUMENT;
182 13 : break;
183 : case VBA_ID_MODULEREADONLY:
184 : OOX_ENSURE_RECORDSIZE( nRecSize == 0 );
185 0 : mbReadOnly = true;
186 0 : break;
187 : case VBA_ID_MODULEPRIVATE:
188 : OOX_ENSURE_RECORDSIZE( nRecSize == 0 );
189 0 : mbPrivate = true;
190 0 : break;
191 : default:
192 : OSL_FAIL( "VbaModule::importDirRecords - unknown module record" );
193 : #undef OOX_ENSURE_RECORDSIZE
194 : }
195 125 : }
196 : OSL_ENSURE( !maName.isEmpty(), "VbaModule::importDirRecords - missing module name" );
197 : OSL_ENSURE( !maStreamName.isEmpty(), "VbaModule::importDirRecords - missing module stream name" );
198 : OSL_ENSURE( mnType != script::ModuleType::UNKNOWN, "VbaModule::importDirRecords - missing module type" );
199 15 : OSL_ENSURE( mnOffset < SAL_MAX_UINT32, "VbaModule::importDirRecords - missing module stream offset" );
200 15 : }
201 :
202 15 : void VbaModule::createAndImportModule( StorageBase& rVbaStrg,
203 : const Reference< container::XNameContainer >& rxBasicLib,
204 : const Reference< container::XNameAccess >& rxDocObjectNA ) const
205 : {
206 15 : OUString aVBASourceCode = readSourceCode( rVbaStrg );
207 15 : createModule( aVBASourceCode, rxBasicLib, rxDocObjectNA );
208 15 : }
209 :
210 0 : void VbaModule::createEmptyModule( const Reference< container::XNameContainer >& rxBasicLib,
211 : const Reference< container::XNameAccess >& rxDocObjectNA ) const
212 : {
213 0 : createModule( OUString(), rxBasicLib, rxDocObjectNA );
214 0 : }
215 :
216 15 : OUString VbaModule::readSourceCode( StorageBase& rVbaStrg ) const
217 : {
218 15 : OUStringBuffer aSourceCode;
219 15 : const static rtl::OUString sUnmatchedRemovedTag( RTL_CONSTASCII_USTRINGPARAM( "Rem removed unmatched Sub/End: " ) );
220 15 : if( !maStreamName.isEmpty() && (mnOffset != SAL_MAX_UINT32) )
221 : {
222 15 : BinaryXInputStream aInStrm( rVbaStrg.openInputStream( maStreamName ), true );
223 : OSL_ENSURE( !aInStrm.isEof(), "VbaModule::readSourceCode - cannot open module stream" );
224 : // skip the 'performance cache' stored before the actual source code
225 15 : aInStrm.seek( mnOffset );
226 : // if stream is still valid, load the source code
227 15 : if( !aInStrm.isEof() )
228 : {
229 : // decompression starts at current stream position of aInStrm
230 15 : VbaInputStream aVbaStrm( aInStrm );
231 : // load the source code line-by-line, with some more processing
232 15 : TextInputStream aVbaTextStrm( mxContext, aVbaStrm, meTextEnc );
233 :
234 : struct ProcedurePair
235 : {
236 : bool bInProcedure;
237 : sal_uInt32 nPos;
238 15 : ProcedurePair() : bInProcedure( false ), nPos( 0 ) {};
239 15 : } procInfo;
240 :
241 480 : while( !aVbaTextStrm.isEof() )
242 : {
243 450 : OUString aCodeLine = aVbaTextStrm.readLine();
244 450 : if( aCodeLine.matchAsciiL( RTL_CONSTASCII_STRINGPARAM( "Attribute " ) ) )
245 : {
246 : // attribute
247 100 : int index = aCodeLine.indexOf( ".VB_ProcData.VB_Invoke_Func = " );
248 100 : if ( index != -1 )
249 : {
250 : // format is
251 : // 'Attribute Procedure.VB_ProcData.VB_Invoke_Func = "*\n14"'
252 : // where 'Procedure' is the procedure name and '*' is the shortcut key
253 : // note: his is only relevant for Excel, seems that
254 : // word doesn't store the shortcut in the module
255 : // attributes
256 0 : int nSpaceIndex = aCodeLine.indexOf(' ');
257 0 : rtl::OUString sProc = aCodeLine.copy( nSpaceIndex + 1, index - nSpaceIndex - 1);
258 : // for Excel short cut key seems limited to cntrl+'a-z, A-Z'
259 0 : rtl::OUString sKey = aCodeLine.copy( aCodeLine.lastIndexOf("= ") + 3, 1 );
260 : // only alpha key valid for key shortcut, however the api will accept other keys
261 0 : if ( !isalpha( (char)sKey[ 0 ] ) )
262 : {
263 : // cntrl modifier is explicit ( but could be cntrl+shift ), parseKeyEvent
264 : // will handle and uppercase letter appropriately
265 0 : rtl::OUString sApiKey = "^";
266 0 : sApiKey += sKey;
267 : try
268 : {
269 0 : KeyEvent aKeyEvent = ooo::vba::parseKeyEvent( sApiKey );
270 0 : ooo::vba::applyShortCutKeyBinding( mxDocModel, aKeyEvent, sProc );
271 : }
272 0 : catch (const Exception&)
273 : {
274 0 : }
275 0 : }
276 : }
277 : }
278 : else
279 : {
280 : // Hack here to weed out any unmatched End Sub / Sub Foo statements.
281 : // The behaviour of the vba ide practically guarantees the case and
282 : // spacing of Sub statement(s). However, indentation can be arbitrary hence
283 : // the trim.
284 350 : rtl::OUString trimLine( aCodeLine.trim() );
285 1731 : if ( mbExecutable && (
286 350 : trimLine.matchAsciiL( RTL_CONSTASCII_STRINGPARAM("Sub ") ) ||
287 346 : trimLine.matchAsciiL( RTL_CONSTASCII_STRINGPARAM("Public Sub ") ) ||
288 346 : trimLine.matchAsciiL( RTL_CONSTASCII_STRINGPARAM("Private Sub ") ) ||
289 339 : trimLine.matchAsciiL( RTL_CONSTASCII_STRINGPARAM("Static Sub ") ) ) )
290 : {
291 : // this should never happen, basic doesn't support nested procedures
292 : // first Sub Foo must be bogus
293 11 : if ( procInfo.bInProcedure )
294 : {
295 : // comment out the line
296 0 : aSourceCode.insert( procInfo.nPos, sUnmatchedRemovedTag );
297 : // mark location of this Sub
298 0 : procInfo.nPos = aSourceCode.getLength();
299 : }
300 : else
301 : {
302 11 : procInfo.bInProcedure = true;
303 11 : procInfo.nPos = aSourceCode.getLength();
304 : }
305 : }
306 339 : else if ( mbExecutable && aCodeLine.trim().matchAsciiL( RTL_CONSTASCII_STRINGPARAM("End Sub")) )
307 : {
308 : // un-matched End Sub
309 11 : if ( !procInfo.bInProcedure )
310 : {
311 0 : aSourceCode.append( sUnmatchedRemovedTag );
312 : }
313 : else
314 : {
315 11 : procInfo.bInProcedure = false;
316 11 : procInfo.nPos = 0;
317 : }
318 : }
319 : // normal source code line
320 350 : if( !mbExecutable )
321 0 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "Rem " ) );
322 350 : aSourceCode.append( aCodeLine ).append( sal_Unicode( '\n' ) );
323 : }
324 465 : }
325 15 : }
326 : }
327 15 : return aSourceCode.makeStringAndClear();
328 : }
329 :
330 15 : void VbaModule::createModule( const OUString& rVBASourceCode,
331 : const Reference< container::XNameContainer >& rxBasicLib,
332 : const Reference< container::XNameAccess >& rxDocObjectNA ) const
333 : {
334 15 : if( maName.isEmpty() )
335 15 : return;
336 :
337 : // prepare the Basic module
338 15 : script::ModuleInfo aModuleInfo;
339 15 : aModuleInfo.ModuleType = mnType;
340 15 : OUStringBuffer aSourceCode;
341 15 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "Rem Attribute VBA_ModuleType=" ) );
342 15 : switch( mnType )
343 : {
344 : case script::ModuleType::NORMAL:
345 2 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "VBAModule" ) );
346 2 : break;
347 : case script::ModuleType::CLASS:
348 0 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "VBAClassModule" ) );
349 0 : break;
350 : case script::ModuleType::FORM:
351 0 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "VBAFormModule" ) );
352 : // hack from old filter, document Basic should know the XModel, but it doesn't
353 0 : aModuleInfo.ModuleObject.set( mxDocModel, UNO_QUERY );
354 0 : break;
355 : case script::ModuleType::DOCUMENT:
356 13 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "VBADocumentModule" ) );
357 : // get the VBA implementation object associated to the document module
358 13 : if( rxDocObjectNA.is() ) try
359 : {
360 0 : aModuleInfo.ModuleObject.set( rxDocObjectNA->getByName( maName ), UNO_QUERY );
361 : }
362 0 : catch (const Exception&)
363 : {
364 : }
365 13 : break;
366 : default:
367 0 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "VBAUnknown" ) );
368 : }
369 15 : aSourceCode.append( sal_Unicode( '\n' ) );
370 15 : if( mbExecutable )
371 : {
372 15 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "Option VBASupport 1\n" ) );
373 15 : if( mnType == script::ModuleType::CLASS )
374 0 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "Option ClassModule\n" ) );
375 : }
376 : else
377 : {
378 : // add a subroutine named after the module itself
379 0 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "Sub " ) ).
380 0 : append( maName.replace( ' ', '_' ) ).append( sal_Unicode( '\n' ) );
381 : }
382 :
383 : // append passed VBA source code
384 15 : aSourceCode.append( rVBASourceCode );
385 :
386 : // close the subroutine named after the module
387 15 : if( !mbExecutable )
388 0 : aSourceCode.appendAscii( RTL_CONSTASCII_STRINGPARAM( "End Sub\n" ) );
389 :
390 : // insert extended module info
391 : try
392 : {
393 15 : Reference< XVBAModuleInfo > xVBAModuleInfo( rxBasicLib, UNO_QUERY_THROW );
394 15 : xVBAModuleInfo->insertModuleInfo( maName, aModuleInfo );
395 : }
396 0 : catch (const Exception&)
397 : {
398 : }
399 :
400 : // insert the module into the passed Basic library
401 : try
402 : {
403 15 : rxBasicLib->insertByName( maName, Any( aSourceCode.makeStringAndClear() ) );
404 : }
405 0 : catch (const Exception&)
406 : {
407 : OSL_FAIL( "VbaModule::createModule - cannot insert module into library" );
408 15 : }
409 : }
410 :
411 : // ============================================================================
412 :
413 : } // namespace ole
414 51 : } // namespace oox
415 :
416 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|