LCOV - code coverage report
Current view: top level - libreoffice/oox/source/ole - vbamodule.cxx (source / functions) Hit Total Coverage
Test: libreoffice_filtered.info Lines: 91 168 54.2 %
Date: 2012-12-27 Functions: 8 19 42.1 %
Legend: Lines: hit not hit

          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: */

Generated by: LCOV version 1.10