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