1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: msvbasic.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_svx.hxx"
34 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */
36 #include <string.h> // memset(), ...
38 #include <io.h> // access()
40 #include <osl/endian.h>
41 #include <rtl/tencinfo.h> //rtl_getTextEncodingFromWindowsCodePage
42 #include "msvbasic.hxx"
45 #include <rtl/ustrbuf.hxx>
46 #include <boost/shared_ptr.hpp>
47 #include <boost/scoped_array.hpp>
48 #include <boost/shared_array.hpp>
49 #include <svtools/filterutils.hxx>
51 using namespace ::com::sun::star::script
;
55 static unsigned int getShift( sal_uInt32 nPos
)
59 return (nPos
<= 0x10) ? 12 : 11;
61 return (nPos
<= 0x40) ? 10 : 9;
64 return (nPos
<= 0x100) ? 8 : 7;
65 else if (nPos
<= 0x800)
66 return (nPos
<= 0x400) ? 6 : 5;
72 SvMemoryStream
*decompressAsStream( SvStream
*pStream
, sal_uInt32 nOffset
, sal_uInt32
*pCompressedLength
= NULL
, sal_uInt32
*pLength
= NULL
)
74 SvMemoryStream
*pResult
;
75 const sal_Int32 nWINDOWLEN
= 4096;
76 pResult
= new SvMemoryStream();
79 unsigned int nPos
= 0;
80 int nLen
, nDistance
, nShift
, nClean
=1;
81 sal_uInt8 aHistory
[ nWINDOWLEN
];
83 pStream
->Seek( nOffset
+ 3 );
85 while( pStream
->Read( &nLeadbyte
, 1 ) )
87 for(int nMask
=0x01; nMask
< 0x100; nMask
= nMask
<<1)
89 // we see if the leadbyte has flagged this location as a dataunit
90 // which is actually a token which must be looked up in the history
91 if( nLeadbyte
& nMask
)
100 //For some reason the division of the token into the length
101 //field of the data to be inserted, and the distance back into
102 //the history differs depending on how full the history is
103 nShift
= getShift( nPos
% nWINDOWLEN
);
105 nLen
= (nToken
& ((1<<nShift
) - 1)) + 3;
106 nDistance
= nToken
>> nShift
;
108 //read the len of data from the history, wrapping around the
109 //nWINDOWLEN boundary if necessary data read from the history
110 //is also copied into the recent part of the history as well.
111 for (int i
= 0; i
< nLen
; i
++)
114 c
= aHistory
[(nPos
-nDistance
-1) % nWINDOWLEN
];
115 aHistory
[nPos
% nWINDOWLEN
] = c
;
121 // special boundary case code, not guarantueed to be correct
122 // seems to work though, there is something wrong with the
123 // compression scheme (or maybe a feature) where when the data
124 // ends on a nWINDOWLEN boundary and the excess bytes in the 8
125 // dataunit list are discarded, and not interpreted as tokens
127 if ((nPos
!= 0) && ((nPos
% nWINDOWLEN
) == 0) && (nClean
))
131 pResult
->Write( aHistory
, nWINDOWLEN
);
134 //This is the normal case for when the data unit is not a
135 //token to be looked up, but instead some normal data which
136 //can be output, and placed in the history.
137 if (pStream
->Read(&aHistory
[nPos
% nWINDOWLEN
],1))
145 if (nPos
% nWINDOWLEN
)
146 pResult
->Write( aHistory
, nPos
% nWINDOWLEN
);
149 if( pCompressedLength
)
150 *pCompressedLength
= nPos
;
153 *pLength
= pResult
->Tell();
162 // also _VBA_PROJECT_VDPI can be used to create a usable
163 // ( and much smaller ) "_VBA_PROJECT" stream
165 // _VBA_PROJECT Stream Version Dependant Project Information
166 // _VBA_PROJECT Stream Version Dependant Project Information
168 class _VBA_PROJECT_VDPI
175 boost::scoped_array
< sal_uInt8
> PerformanceCache
;
176 sal_Int32 PerformanceCacheSize
;
177 _VBA_PROJECT_VDPI(): Reserved1( 0x61CC), Version( 0xFFFF ), Reserved2(0x0), Reserved3(0x0), PerformanceCacheSize(0) {}
180 PerformanceCacheSize
= 0;
183 void write( SvStream
* pStream
)
185 *pStream
<< Reserved1
<< Version
<< Reserved2
<< Reserved3
;
186 if ( PerformanceCacheSize
)
188 PerformanceCache
.reset( new sal_uInt8
[ PerformanceCacheSize
] );
189 pStream
->Read( PerformanceCache
.get(), PerformanceCacheSize
);
194 class ProjectSysKindRecord
200 ProjectSysKindRecord(): Id(0x1), Size(0x4), SysKind( 0x1 ) {}
201 void read( SvStream
* pStream
)
203 *pStream
>> Id
>> Size
>> SysKind
;
207 class ProjectLcidRecord
214 ProjectLcidRecord() : Id( 0x2 ), Size( 0x4 ), Lcid( 0x409 ) {}
215 void read( SvStream
* pStream
)
217 OSL_TRACE("ProjectLcidRecord [0x%x]", pStream
->Tell() );
218 *pStream
>> Id
>> Size
>> Lcid
;
222 class ProjectLcidInvokeRecord
226 sal_Int32 LcidInvoke
;
228 ProjectLcidInvokeRecord() : Id( 0x14 ), Size( 0x4 ), LcidInvoke( 0x409 ) {}
229 void read( SvStream
* pStream
)
231 OSL_TRACE("ProjectLcidInvokeRecord [0x%x]", pStream
->Tell() );
232 *pStream
>> Id
>> Size
>> LcidInvoke
;
236 class ProjectCodePageRecord
242 // #FIXME get a better default for the CodePage
243 ProjectCodePageRecord() : Id( 0x03 ), Size( 0x2 ), CodePage( 0x0 ) {}
244 void read( SvStream
* pStream
)
246 OSL_TRACE("ProjectCodePageRecord [0x%x]", pStream
->Tell() );
247 *pStream
>> Id
>> Size
>> CodePage
;
250 class ProjectNameRecord
254 sal_Int32 SizeOfProjectName
;
255 rtl::OUString ProjectName
;
256 ProjectNameRecord() : Id( 0x04 ), SizeOfProjectName( 0x0 ){}
260 void read( SvStream
* pStream
)
262 OSL_TRACE("ProjectNameRecord [0x%x]", pStream
->Tell() );
263 *pStream
>> Id
>> SizeOfProjectName
;
265 if ( SizeOfProjectName
)
267 boost::scoped_array
< sal_uInt8
> pProjectName( new sal_uInt8
[ SizeOfProjectName
] );
268 OSL_TRACE("ProjectNameRecord about to read name from [0x%x], size %d", pStream
->Tell(), SizeOfProjectName
);
269 pStream
->Read( pProjectName
.get(), SizeOfProjectName
);
270 ProjectName
= svt::BinFilterUtils::CreateOUStringFromStringArray( reinterpret_cast< const char* >( pProjectName
.get() ), SizeOfProjectName
);
275 class ProjectDocStringRecord
279 sal_Int32 SizeOfDocString
;
281 sal_Int32 SizeOfDocStringUnicode
;
282 rtl::OUString DocString
;
283 rtl::OUString DocStringUnicode
;
285 ProjectDocStringRecord() : Id( 0x5 ), SizeOfDocString( 0x0 ), Reserved( 0x0 ), SizeOfDocStringUnicode( 0 ){}
287 ~ProjectDocStringRecord()
290 void read( SvStream
* pStream
)
292 OSL_TRACE("ProjectDocStringRecord [0x%x]", pStream
->Tell() );
293 *pStream
>> Id
>> SizeOfDocString
;
296 boost::scoped_array
< sal_uInt8
> pDocString( new sal_uInt8
[ SizeOfDocString
] );
297 pStream
->Read( pDocString
.get(), SizeOfDocString
);
299 DocString
= svt::BinFilterUtils::CreateOUStringFromStringArray( reinterpret_cast< const char* >( pDocString
.get() ), SizeOfDocString
);
301 *pStream
>> Reserved
>> SizeOfDocStringUnicode
;
303 boost::scoped_array
< sal_uInt8
> pDocStringUnicode( new sal_uInt8
[ SizeOfDocStringUnicode
] );
305 pStream
->Read( pDocStringUnicode
.get(), SizeOfDocStringUnicode
);
306 DocStringUnicode
= svt::BinFilterUtils::CreateOUStringFromUniStringArray( reinterpret_cast< const char* >( pDocStringUnicode
.get() ), SizeOfDocString
);
312 class ProjectHelpFilePath
316 sal_Int32 SizeOfHelpFile1
;
317 boost::scoped_array
< sal_uInt8
> HelpFile1
;
319 sal_Int32 SizeOfHelpFile2
;
320 boost::scoped_array
< sal_uInt8
> HelpFile2
;
322 ProjectHelpFilePath() : Id( 0x06 ), SizeOfHelpFile1(0), Reserved(0x0), SizeOfHelpFile2(0) {}
323 ~ProjectHelpFilePath()
327 void read( SvStream
* pStream
)
329 OSL_TRACE("ProjectHelpFilePath [0x%x]", pStream
->Tell() );
330 *pStream
>> Id
>> SizeOfHelpFile1
;
332 HelpFile1
.reset( new sal_uInt8
[ SizeOfHelpFile1
] );
333 pStream
->Read( HelpFile1
.get(), SizeOfHelpFile1
);
335 *pStream
>> Reserved
>> SizeOfHelpFile2
;
337 HelpFile2
.reset( new sal_uInt8
[ SizeOfHelpFile2
] );
338 pStream
->Read( HelpFile2
.get(), SizeOfHelpFile2
);
343 class ProjectHelpContextRecord
348 sal_Int32 HelpContext
;
350 ProjectHelpContextRecord() : Id( 0x7 ), Size( 0x4 ), HelpContext( 0 ) {}
351 void read( SvStream
* pStream
)
354 OSL_TRACE("ProjectHelpContextRecord [0x%x]", pStream
->Tell() );
355 *pStream
>> Id
>> Size
>> HelpContext
;
360 class ProjectLibFlagsRecord
364 sal_Int32 ProjectLibFlags
;
367 ProjectLibFlagsRecord() : Id( 0x8 ), Size( 0x4 ), ProjectLibFlags( 0x0 ) {}
368 void read( SvStream
* pStream
)
370 OSL_TRACE("ProjectLibFlagsRecord [0x%x]", pStream
->Tell() );
371 *pStream
>> Id
>> Size
>> ProjectLibFlags
;
375 class ProjectVersionRecord
380 sal_Int32 VersionMajor
;
381 sal_Int16 VersionMinor
;
382 ProjectVersionRecord() : Id( 0x9 ), Reserved( 0x4 ), VersionMajor( 0x1 ), VersionMinor( 0 ) {}
383 void read( SvStream
* pStream
)
385 OSL_TRACE("ProjectVersionRecord [0x%x]", pStream
->Tell() );
386 *pStream
>> Id
>> Reserved
>> VersionMajor
>> VersionMinor
;
390 class ProjectConstantsRecord
393 sal_Int32 SizeOfConstants
;
394 boost::scoped_array
< sal_uInt8
> Constants
;
396 sal_Int32 SizeOfConstantsUnicode
;
397 boost::scoped_array
< sal_uInt8
> ConstantsUnicode
;
399 ProjectConstantsRecord() : Id( 0xC ), SizeOfConstants( 0 ), Constants( 0 ), Reserved( 0x3C ), SizeOfConstantsUnicode( 0 ), ConstantsUnicode(0) {}
401 ~ProjectConstantsRecord()
405 void read( SvStream
* pStream
)
407 OSL_TRACE("ProjectConstantsRecord [0x%x]", pStream
->Tell() );
408 *pStream
>> Id
>> SizeOfConstants
;
409 Constants
.reset( new sal_uInt8
[ SizeOfConstants
] );
411 pStream
->Read( Constants
.get(), SizeOfConstants
);
413 *pStream
>> Reserved
;
415 *pStream
>> SizeOfConstantsUnicode
;
417 ConstantsUnicode
.reset( new sal_uInt8
[ SizeOfConstantsUnicode
] );
418 pStream
->Read( ConstantsUnicode
.get(), SizeOfConstantsUnicode
);
423 class ReferenceNameRecord
427 sal_Int32 SizeOfName
;
430 sal_Int32 SizeOfNameUnicode
;
431 rtl::OUString NameUnicode
;
433 ReferenceNameRecord() : Id( 0x16 ), SizeOfName( 0 ), Reserved( 0x3E ), SizeOfNameUnicode( 0 ){}
434 ~ReferenceNameRecord()
438 void read( SvStream
* pStream
)
440 OSL_TRACE("NameRecord [0x%x]", pStream
->Tell() );
441 *pStream
>> Id
>> SizeOfName
;
443 boost::scoped_array
< sal_uInt8
> pName( new sal_uInt8
[ SizeOfName
] );
445 pStream
->Read( pName
.get(), SizeOfName
);
446 Name
= svt::BinFilterUtils::CreateOUStringFromStringArray( reinterpret_cast< const char* >( pName
.get() ), SizeOfName
);
448 *pStream
>> Reserved
>> SizeOfNameUnicode
;
450 boost::scoped_array
< sal_uInt8
> pNameUnicode( new sal_uInt8
[ SizeOfNameUnicode
] );
451 pStream
->Read( pNameUnicode
.get(), SizeOfNameUnicode
);
452 NameUnicode
= svt::BinFilterUtils::CreateOUStringFromUniStringArray( reinterpret_cast< const char* >( pNameUnicode
.get() ), SizeOfName
);
457 // Baseclass for ReferenceControlRecord, ReferenceRegisteredRecord, ReferenceProjectRecord
460 class BaseReferenceRecord
463 virtual ~BaseReferenceRecord(){}
464 virtual bool read( SvStream
* pStream
) = 0;
465 virtual void import( VBA_Impl
& ){}
469 class ReferenceProjectRecord
: public BaseReferenceRecord
474 sal_uInt32 SizeOfLibidAbsolute
;
475 sal_uInt32 SizeOfLibidRelative
;
476 sal_uInt32 MajorVersion
;
477 sal_uInt16 MinorVersion
;
478 rtl::OUString AbsoluteLibid
;
479 rtl::OUString RelativeLibid
;
481 virtual bool read( SvStream
* pStream
);
482 virtual void import( VBA_Impl
& rDir
);
483 ReferenceProjectRecord();
484 ~ReferenceProjectRecord();
487 ReferenceProjectRecord::ReferenceProjectRecord() : Id( 0x000E ), Size( 0 ), SizeOfLibidAbsolute( 0 ), SizeOfLibidRelative( 0 ), MajorVersion( 0 ), MinorVersion( 0 )
491 ReferenceProjectRecord::~ReferenceProjectRecord()
495 bool ReferenceProjectRecord::read( SvStream
* pStream
)
497 OSL_TRACE("ReferenceProjectRecord [0x%x]", pStream
->Tell() );
498 *pStream
>> Id
>> Size
>> SizeOfLibidAbsolute
;
500 boost::scoped_array
< sal_uInt8
> pLibidAbsolute( new sal_uInt8
[ SizeOfLibidAbsolute
] );
501 OSL_TRACE("ReferenceProjectRecord about to read LibidAbsolute at [0x%x]", pStream
->Tell() );
502 pStream
->Read( pLibidAbsolute
.get(), SizeOfLibidAbsolute
);
504 *pStream
>> SizeOfLibidRelative
;
506 boost::scoped_array
< sal_uInt8
> pLibidRelative( new sal_uInt8
[ SizeOfLibidRelative
] );
507 OSL_TRACE("ReferenceProjectRecord about to read LibidRelative at [0x%x]", pStream
->Tell() );
508 pStream
->Read( pLibidRelative
.get(), SizeOfLibidRelative
);
510 *pStream
>> MajorVersion
>> MinorVersion
;
512 // array size is ORed with SVX_MSOCX_COMPRESSED to force processing of ascii bytes ( and not
514 // the offset of 3 is needed to skip the ProjectReference "*\" and project kind ( 0x4[1-4] ) info.
516 AbsoluteLibid
= svt::BinFilterUtils::CreateOUStringFromStringArray( reinterpret_cast< const char* >( pLibidAbsolute
.get() + 3 ), (SizeOfLibidAbsolute
- 3 ) );
517 RelativeLibid
= svt::BinFilterUtils::CreateOUStringFromStringArray( reinterpret_cast< const char* >( pLibidRelative
.get() + 3 ), ( SizeOfLibidRelative
-3 ) );
519 OSL_TRACE("ReferenceProjectRecord - absolute path %s", rtl::OUStringToOString( AbsoluteLibid
, RTL_TEXTENCODING_UTF8
).getStr() );
520 OSL_TRACE("ReferenceProjectRecord - relative path %s", rtl::OUStringToOString( RelativeLibid
, RTL_TEXTENCODING_UTF8
).getStr() );
524 void ReferenceProjectRecord::import( VBA_Impl
& rDir
)
526 rDir
.AddProjectReference( AbsoluteLibid
);
529 class ReferenceRegisteredRecord
: public BaseReferenceRecord
534 sal_uInt32 SizeOfLibid
;
535 boost::scoped_array
< sal_uInt8
> pLibid
;
539 ReferenceRegisteredRecord();
540 ~ReferenceRegisteredRecord();
541 bool read( SvStream
* pStream
);
544 ReferenceRegisteredRecord::ReferenceRegisteredRecord() : Id( 0x000D ), Size( 0 ), SizeOfLibid( 0 ), Reserved1( 0 ), Reserved2( 0 )
548 ReferenceRegisteredRecord::~ReferenceRegisteredRecord()
553 ReferenceRegisteredRecord::read( SvStream
* pStream
)
555 OSL_TRACE("ReferenceRegisteredRecord [0x%x]", pStream
->Tell() );
556 *pStream
>> Id
>> Size
>> SizeOfLibid
;
559 pLibid
.reset( new sal_uInt8
[ SizeOfLibid
] );
560 pStream
->Read( pLibid
.get(), SizeOfLibid
);
562 *pStream
>> Reserved1
>> Reserved2
;
566 class ReferenceOriginalRecord
570 sal_uInt32 SizeOfLibOriginal
;
571 boost::scoped_array
< sal_uInt8
> pLibidOriginal
;
574 ReferenceOriginalRecord() : Id( 0x033 ), SizeOfLibOriginal( 0 )
578 ~ReferenceOriginalRecord()
582 void read( SvStream
* pStream
)
584 *pStream
>> Id
>> SizeOfLibOriginal
;
585 if ( SizeOfLibOriginal
)
587 pLibidOriginal
.reset( new sal_uInt8
[ SizeOfLibOriginal
] );
588 pStream
->Read( pLibidOriginal
.get(), SizeOfLibOriginal
);
594 class ReferenceControlRecord
: public BaseReferenceRecord
597 std::auto_ptr
< ReferenceOriginalRecord
> OriginalRecord
;
599 sal_uInt32 SizeTwiddled
;
600 sal_uInt32 SizeOfLibidTwiddled
;
601 boost::shared_array
< sal_uInt8
> LibidTwiddled
;
602 sal_uInt32 Reserved1
;
603 sal_uInt16 Reserved2
;
604 std::auto_ptr
< ReferenceNameRecord
> NameRecordExtended
;// Optional
605 sal_uInt16 Reserved3
;
606 sal_uInt32 SizeExtended
;
607 sal_uInt32 SizeOfLibidExtended
;
608 boost::shared_array
< sal_uInt8
> LibidExtended
;
609 sal_uInt32 Reserved4
;
610 sal_uInt16 Reserved5
;
611 sal_uInt8 OriginalTypeLib
[ 16 ];
614 ReferenceControlRecord() : Id( 0x2F ), SizeTwiddled( 0 ), SizeOfLibidTwiddled( 0 ), Reserved1( 0 ), Reserved2( 0 ), Reserved3( 0x30 ), SizeExtended( 0 ), SizeOfLibidExtended( 0 ), Reserved4( 0 ), Reserved5( 0 ), Cookie( 0 )
616 for( int i
= 0; i
< 16; ++i
)
617 OriginalTypeLib
[ i
] = 0;
620 ~ReferenceControlRecord()
624 bool read( SvStream
* pStream
)
626 OSL_TRACE("ReferenceControlRecord [0x%x]", pStream
->Tell() );
627 long nPos
= pStream
->Tell();
630 pStream
->Seek( nPos
); // point before the peeked Id
631 if ( Id
== 0x33 ) // we have an OriginalRecord
633 OriginalRecord
.reset( new ReferenceOriginalRecord() );
634 OriginalRecord
->read( pStream
);
636 *pStream
>> Id
>> SizeTwiddled
>> SizeOfLibidTwiddled
;
638 if ( SizeOfLibidTwiddled
)
640 LibidTwiddled
.reset( new sal_uInt8
[ SizeOfLibidTwiddled
] );
641 pStream
->Read( LibidTwiddled
.get(), SizeOfLibidTwiddled
);
644 *pStream
>> Reserved1
>> Reserved2
;
646 nPos
= pStream
->Tell();
647 // peek at the id for optional NameRecord
650 if ( nTmpId
== 0x30 )
656 pStream
->Seek( nPos
);
657 NameRecordExtended
.reset( new ReferenceNameRecord() );
658 NameRecordExtended
->read( pStream
);
659 *pStream
>> Reserved3
;
661 *pStream
>> SizeExtended
>> SizeOfLibidExtended
;
665 LibidExtended
.reset( new sal_uInt8
[ SizeOfLibidExtended
] );
666 pStream
->Read( LibidExtended
.get(), SizeOfLibidExtended
);
669 *pStream
>> Reserved4
;
670 *pStream
>> Reserved5
;
672 pStream
->Read( OriginalTypeLib
, sizeof( OriginalTypeLib
) );
679 class ReferenceRecord
: public BaseReferenceRecord
682 // NameRecord is Optional
683 std::auto_ptr
< ReferenceNameRecord
> NameRecord
;
684 std::auto_ptr
< BaseReferenceRecord
> aReferenceRecord
;
691 // false return would mean failed to read Record e.g. end of array encountered
692 // Note: this read routine will make sure the stream is pointing to where it was the
693 // method was called )
695 bool read( SvStream
* pStream
)
697 OSL_TRACE("ReferenceRecord [0x%x]", pStream
->Tell() );
699 long nStart
= pStream
->Tell();
704 pStream
->Seek( nPos
); // place back before Id
705 if ( Id
== 0x16 ) // Optional NameRecord
707 NameRecord
.reset( new ReferenceNameRecord() );
708 NameRecord
->read( pStream
);
710 else if ( Id
== 0x0f )
712 pStream
->Seek( nStart
);
714 return bRead
; // start of module, terminate read
717 nPos
= pStream
->Tell(); // mark position, peek at next Id
719 pStream
->Seek( nPos
); // place back before Id
724 aReferenceRecord
.reset( new ReferenceRegisteredRecord() );
727 aReferenceRecord
.reset( new ReferenceProjectRecord() );
731 aReferenceRecord
.reset( new ReferenceControlRecord() );
735 OSL_TRACE("Big fat error, unknown ID 0x%x", Id
);
739 aReferenceRecord
->read( pStream
);
743 void import( VBA_Impl
& rVBA
)
745 if ( aReferenceRecord
.get() )
746 aReferenceRecord
->import( rVBA
);
754 ProjectSysKindRecord mSysKindRec
;
755 ProjectLcidRecord mLcidRec
;
756 ProjectLcidInvokeRecord mLcidInvokeRec
;
757 ProjectCodePageRecord mCodePageRec
;
758 ProjectNameRecord mProjectNameRec
;
759 ProjectDocStringRecord mDocStringRec
;
760 ProjectHelpFilePath mHelpFileRec
;
761 ProjectHelpContextRecord mHelpContextRec
;
762 ProjectLibFlagsRecord mLibFlagsRec
;
763 ProjectVersionRecord mVersionRec
;
764 ProjectConstantsRecord mConstantsRecord
;
765 std::vector
< ReferenceRecord
* > ReferenceArray
;
770 for ( std::vector
< ReferenceRecord
* >::iterator it
= ReferenceArray
.begin(); it
!= ReferenceArray
.end(); ++it
)
775 void read( SvStream
* pStream
)
777 sal_Int32 nPos
= pStream
->Tell();
779 std::ofstream
aDump("dir.dump");
780 while ( !pStream
->IsEof() )
788 pStream
->Seek( nPos
);
789 readProjectInformation( pStream
);
790 readProjectReferenceInformation( pStream
);
793 void readProjectReferenceInformation( SvStream
* pStream
)
795 bool bKeepReading
= true;
796 while( bKeepReading
)
798 ReferenceRecord
* pRef
= new ReferenceRecord();
799 bKeepReading
= pRef
->read( pStream
);
801 ReferenceArray
.push_back( pRef
);
805 void readProjectInformation( SvStream
* pStream
)
807 mSysKindRec
.read( pStream
);
808 mLcidRec
.read( pStream
);
809 mLcidInvokeRec
.read( pStream
);
810 mCodePageRec
.read( pStream
);
811 mProjectNameRec
.read( pStream
);
812 mDocStringRec
.read( pStream
);
813 mHelpFileRec
.read( pStream
);
814 mHelpContextRec
.read( pStream
);
815 mLibFlagsRec
.read( pStream
);
816 mVersionRec
.read( pStream
);
817 sal_Int32 nPos
= pStream
->Tell();
822 pStream
->Seek( nPos
);
823 mConstantsRecord
.read( pStream
);
825 OSL_TRACE("After Information pos is 0x%x", pStream
->Tell() );
828 void import( VBA_Impl
& rVBA
)
830 // get project references
831 for ( std::vector
< ReferenceRecord
* >::iterator it
= ReferenceArray
.begin(); it
!= ReferenceArray
.end(); ++it
)
832 (*it
)->import( rVBA
);
833 rVBA
.SetProjectName( mProjectNameRec
.ProjectName
);
840 A few urls which may in the future be of some use
841 http://www.virusbtn.com/vb2000/Programme/papers/bontchev.pdf
845 * The VBA class provides a set of methods to handle Visual Basic For
846 * Applications streams, the constructor is given the root ole2 stream
847 * of the document, Open reads the VBA project file and figures out
848 * the number of VBA streams, and the offset of the data within them.
849 * Decompress decompresses a particular numbered stream, NoStreams returns
850 * this number, and StreamName can give you the streams name. Decompress
851 * will call Output when it has a 4096 byte collection of data to output,
852 * and also with the final remainder of data if there is still some left
853 * at the end of compression. Output is virtual to allow custom handling
854 * of each chunk of decompressed data. So inherit from this to do something
855 * useful with the data.
859 const int MINVBASTRING
= 6;
861 VBA_Impl::VBA_Impl(SvStorage
&rIn
, bool bCmmntd
)
863 sComment(RTL_CONSTASCII_USTRINGPARAM("Rem ")),
864 xStor(&rIn
), pOffsets(0), nOffsets(0), meCharSet(RTL_TEXTENCODING_MS_1252
),
865 bCommented(bCmmntd
), mbMac(false), nLines(0)
869 VBA_Impl::~VBA_Impl()
872 for (ULONG i
=0;i
<aVBAStrings
.GetSize();++i
)
873 delete aVBAStrings
.Get(i
);
876 sal_uInt8
VBA_Impl::ReadPString(SvStorageStreamRef
&xVBAProject
,
879 sal_uInt16 nIdLen
, nOut16
;
880 sal_uInt8 nType
= 0, nOut8
;
883 *xVBAProject
>> nIdLen
;
885 if (nIdLen
< MINVBASTRING
) //Error recovery
886 xVBAProject
->SeekRel(-2); //undo 2 byte len
889 for(sal_uInt16 i
=0; i
< nIdLen
/ (bIsUnicode
? 2 : 1); i
++)
892 *xVBAProject
>> nOut16
;
895 *xVBAProject
>> nOut8
;
898 sReference
+= nOut16
;
901 if ((nOut16
== 'G') || (nOut16
== 'H') || (nOut16
== 'C') ||
904 nType
= static_cast<sal_uInt8
>(nOut16
);
908 //Error recovery, 2byte len + 3 characters of used type
909 xVBAProject
->SeekRel(-(2 + 3 * (bIsUnicode
? 2 : 1)));
914 maReferences
.push_back(sReference
);
919 void VBA_Impl::Output( int nLen
, const sal_uInt8
*pData
)
922 Each StarBasic module is tragically limited to the maximum len of a
923 string and WordBasic is not, so each overlarge module must be split
925 String
sTemp((const sal_Char
*)pData
, (xub_StrLen
)nLen
,
927 int nTmp
= sTemp
.GetTokenCount('\x0D');
928 int nIndex
= aVBAStrings
.GetSize()-1;
929 if (aVBAStrings
.Get(nIndex
)->Len() +
930 nLen
+ ((nLines
+nTmp
) * sComment
.Len()) >= STRING_MAXLEN
)
932 //DBG_ASSERT(0,"New Module String\n");
933 //we are too large for our boots, break out into another
937 aVBAStrings
.SetSize(nIndex
+1);
938 aVBAStrings
.Put(nIndex
,new String
);
940 *(aVBAStrings
.Get(nIndex
)) += sTemp
;
945 int VBA_Impl::ReadVBAProject(const SvStorageRef
&rxVBAStorage
)
947 SvStorageStreamRef xVBAProject
;
948 xVBAProject
= rxVBAStorage
->OpenSotStream(
949 String( RTL_CONSTASCII_USTRINGPARAM( "_VBA_PROJECT" ) ),
950 STREAM_STD_READ
| STREAM_NOCREATE
);
952 SvStorageStreamRef xDir
= rxVBAStorage
->OpenSotStream(
953 String( RTL_CONSTASCII_USTRINGPARAM( "dir" ) ),
954 STREAM_STD_READ
| STREAM_NOCREATE
);
955 // disable read and import of Dir stream bits, e.g. project references and
956 // project name for 3.1 ( a bit unstable yet )
958 // decompress the stream
959 std::auto_ptr
< SvMemoryStream
> xCmpDir
;
960 xCmpDir
.reset( MSLZSS::decompressAsStream( xDir
, 0 ) );
961 // try to parse the dir stream
963 dDump
.read( xCmpDir
.get() );
964 dDump
.import( *this );
966 if( !xVBAProject
.Is() || SVSTREAM_OK
!= xVBAProject
->GetError() )
968 DBG_WARNING("Not able to find vba project, cannot find macros");
972 static const sal_uInt8 aKnownId
[] = {0xCC, 0x61};
974 xVBAProject
->Read( aId
, sizeof(aId
) );
975 if (memcmp( aId
, aKnownId
, sizeof(aId
)))
977 DBG_WARNING("unrecognized VBA macro project type");
981 static const sal_uInt8 aOffice2003LE_2
[] =
983 0x79, 0x00, 0x00, 0x01, 0x00, 0xFF
986 static const sal_uInt8 aOffice2003LE
[] =
988 0x76, 0x00, 0x00, 0x01, 0x00, 0xFF
991 static const sal_uInt8 aOfficeXPLE
[] =
993 0x73, 0x00, 0x00, 0x01, 0x00, 0xFF
996 static const sal_uInt8 aOfficeXPBE
[] =
998 0x63, 0x00, 0x00, 0x0E, 0x00, 0xFF
1001 static const sal_uInt8 aOffice2000LE
[] =
1003 0x6D, 0x00, 0x00, 0x01, 0x00, 0xFF
1005 static const sal_uInt8 aOffice98BE
[] =
1007 0x60, 0x00, 0x00, 0x0E, 0x00, 0xFF
1009 static const sal_uInt8 aOffice97LE
[] =
1011 0x5E, 0x00, 0x00, 0x01, 0x00, 0xFF
1013 sal_uInt8 aProduct
[6];
1014 xVBAProject
->Read( aProduct
, sizeof(aProduct
) );
1017 if (!(memcmp(aProduct
, aOffice2003LE
, sizeof(aProduct
))) ||
1018 !(memcmp(aProduct
, aOffice2003LE_2
, sizeof(aProduct
))) )
1020 xVBAProject
->SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN
);
1023 else if (!(memcmp(aProduct
, aOfficeXPLE
, sizeof(aProduct
))))
1025 xVBAProject
->SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN
);
1028 else if (!(memcmp(aProduct
, aOfficeXPBE
, sizeof(aProduct
))))
1030 xVBAProject
->SetNumberFormatInt( NUMBERFORMAT_INT_BIGENDIAN
);
1034 else if (!(memcmp(aProduct
, aOffice2000LE
, sizeof(aProduct
))))
1036 xVBAProject
->SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN
);
1039 else if (!(memcmp(aProduct
, aOffice98BE
, sizeof(aProduct
))))
1041 xVBAProject
->SetNumberFormatInt( NUMBERFORMAT_INT_BIGENDIAN
);
1045 else if (!(memcmp(aProduct
, aOffice97LE
, sizeof(aProduct
))))
1047 xVBAProject
->SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN
);
1052 switch (aProduct
[3])
1055 xVBAProject
->SetNumberFormatInt(NUMBERFORMAT_INT_LITTLEENDIAN
);
1057 DBG_ASSERT(!this, "unrecognized VBA macro version, report to cmc. Guessing at unicode little endian");
1060 xVBAProject
->SetNumberFormatInt(NUMBERFORMAT_INT_BIGENDIAN
);
1063 DBG_ASSERT(!this, "unrecognized VBA macro version, report to cmc. Guessing at 8bit big endian");
1066 DBG_ASSERT(!this, "totally unrecognized VBA macro version, report to cmc");
1071 sal_uInt32 nLidA
; //Language identifiers
1073 sal_uInt16 nCharSet
;
1075 sal_uInt32 nUnknownB
;
1076 sal_uInt32 nUnknownC
;
1081 *xVBAProject
>> nLidA
>> nLidB
>> nCharSet
>> nLenA
>> nUnknownB
;
1082 *xVBAProject
>> nUnknownC
>> nLenB
>> nLenC
>> nLenD
;
1084 meCharSet
= rtl_getTextEncodingFromWindowsCodePage(nCharSet
);
1086 DBG_ASSERT(meCharSet
!= RTL_TEXTENCODING_DONTKNOW
,
1087 "don't know what vba charset to use");
1088 if (meCharSet
== RTL_TEXTENCODING_DONTKNOW
)
1089 meCharSet
= RTL_TEXTENCODING_MS_1252
;
1093 DBG_WARNING("Warning VBA number is different, please report");
1098 A sequence of string that are prepended with a len and then begin with G
1099 or H, there are also those that begin with C or D. If a string begins with
1100 C or D, it is really two strings, one right after the other. Each string
1101 then has a 12 bytes suffix
1103 Recognizing the end of the sequence is done by finding a str len of < 6
1104 which does not appear to be the beginning of an object id. Admittedly this
1105 isn't a great test, but nothing in the header appears to count the number
1106 of strings, and nothing else seems to match. So it'll have to do, its
1107 protected by a number of secondry tests to prove its a valid string, and
1108 everything gives up if this isn't proven.
1110 bool bPredictsTrailingTwenty
= false;
1113 sal_uInt8 nType
= ReadPString(xVBAProject
,bIsUnicode
);
1114 //Type C and D seem to come as pairs, so skip the following one
1115 if (nType
== 'C' || nType
== 'D')
1117 nType
= ReadPString(xVBAProject
,bIsUnicode
);
1118 DBG_ASSERT( nType
== 'C' || nType
== 'D',
1119 "VBA: This must be a 'C' or 'D' string!" );
1120 if (nType
!= 'C' && nType
!= 'D')
1125 xVBAProject
->SeekRel(10);
1126 sal_uInt16 nPredictsTrailingTwenty
;
1127 *xVBAProject
>> nPredictsTrailingTwenty
;
1128 if (nPredictsTrailingTwenty
)
1129 bPredictsTrailingTwenty
= true;
1130 if (bPredictsTrailingTwenty
)
1132 sal_uInt16 nTestIsNotString
;
1133 *xVBAProject
>> nTestIsNotString
;
1134 if (nTestIsNotString
< MINVBASTRING
)
1136 DBG_ASSERT(nTestIsNotString
<= 1,
1137 "Haven't seen a len like this in VBA, report to CMC");
1138 xVBAProject
->SeekRel(18);
1139 bPredictsTrailingTwenty
= false;
1142 xVBAProject
->SeekRel(-2);
1147 *xVBAProject
>> nInt16s
;
1148 DBG_ASSERT( nInt16s
>= 0, "VBA: Bad no of records in VBA Project, panic!" );
1152 xVBAProject
->SeekRel(2*nInt16s
);
1155 *xVBAProject
>> nInt32s
;
1156 DBG_ASSERT( nInt32s
>= 0, "VBA: Bad no of records in VBA Project, panic!" );
1159 xVBAProject
->SeekRel(4*nInt32s
);
1161 xVBAProject
->SeekRel(2);
1162 for(int k
=0;k
<3;k
++)
1165 *xVBAProject
>> nLen
;
1167 xVBAProject
->SeekRel(nLen
);
1169 xVBAProject
->SeekRel(100); //Seems fixed len
1171 *xVBAProject
>> nOffsets
;
1172 DBG_ASSERT( nOffsets
!= 0xFFFF, "VBA: Bad nOffsets, panic!!" );
1173 if ((nOffsets
== 0xFFFF) || (nOffsets
== 0))
1175 pOffsets
= new VBAOffset_Impl
[ nOffsets
];
1178 for( i
=0; i
< nOffsets
; i
++)
1181 *xVBAProject
>> nLen
;
1185 sal_Unicode
* pBuf
= pOffsets
[i
].sName
.AllocBuffer( nLen
/ 2 );
1186 xVBAProject
->Read( (sal_Char
*)pBuf
, nLen
);
1188 #ifdef OSL_BIGENDIAN
1189 for( j
= 0; j
< nLen
/ 2; ++j
, ++pBuf
)
1190 *pBuf
= SWAPSHORT( *pBuf
);
1191 #endif // ifdef OSL_BIGENDIAN
1195 ByteString aByteStr
;
1196 sal_Char
* pByteData
= aByteStr
.AllocBuffer( nLen
);
1197 sal_Size nWasRead
= xVBAProject
->Read( pByteData
, nLen
);
1198 if( nWasRead
!= nLen
)
1199 aByteStr
.ReleaseBufferAccess();
1200 pOffsets
[i
].sName
+= String( aByteStr
, meCharSet
);
1203 *xVBAProject
>> nLen
;
1204 xVBAProject
->SeekRel( nLen
);
1206 //begin section, another problem area
1207 *xVBAProject
>> nLen
;
1208 if ( nLen
== 0xFFFF)
1210 xVBAProject
->SeekRel(2);
1211 *xVBAProject
>> nLen
;
1212 xVBAProject
->SeekRel( nLen
);
1215 xVBAProject
->SeekRel( nLen
+2 );
1217 *xVBAProject
>> nLen
;
1218 DBG_ASSERT( nLen
== 0xFFFF, "VBA: Bad field in VBA Project, panic!!" );
1219 if ( nLen
!= 0xFFFF)
1222 xVBAProject
->SeekRel(6);
1223 sal_uInt16 nOctects
;
1224 *xVBAProject
>> nOctects
;
1225 for(j
=0;j
<nOctects
;j
++)
1226 xVBAProject
->SeekRel(8);
1228 xVBAProject
->SeekRel(5);
1231 *xVBAProject
>> pOffsets
[i
].nOffset
;
1232 xVBAProject
->SeekRel(2);
1239 /* #117718# For a given Module name return its type,
1240 * Form, Class, Document, Normal or Unknown
1244 ModType
VBA_Impl::GetModuleType( const UniString
& rModuleName
)
1246 ModuleTypeHash::iterator iter
= mhModHash
.find( rModuleName
);
1247 ModuleTypeHash::iterator iterEnd
= mhModHash
.end();
1248 if ( iter
!= iterEnd
)
1250 return iter
->second
;
1252 return ModuleType::Unknown
;
1255 bool VBA_Impl::Open( const String
&rToplevel
, const String
&rSublevel
)
1257 /* beginning test for vba stuff */
1259 SvStorageRef xMacros
= xStor
->OpenSotStorage( rToplevel
,
1260 STREAM_READWRITE
| STREAM_NOCREATE
|
1261 STREAM_SHARE_DENYALL
);
1262 if( !xMacros
.Is() || SVSTREAM_OK
!= xMacros
->GetError() )
1264 DBG_WARNING("No Macros Storage");
1265 OSL_TRACE("No Macros Storage");
1269 xVBA
= xMacros
->OpenSotStorage( rSublevel
,
1270 STREAM_READWRITE
| STREAM_NOCREATE
|
1271 STREAM_SHARE_DENYALL
);
1272 if( !xVBA
.Is() || SVSTREAM_OK
!= xVBA
->GetError() )
1274 DBG_WARNING("No Visual Basic in Storage");
1275 OSL_TRACE("No Visual Basic in Storage");
1279 if (ReadVBAProject(xVBA
))
1283 * Information regarding the type of module is contained in the
1284 * "PROJECT" stream, this stream consists of a number of ascii lines
1285 * entries are of the form Key=Value, the ones that we are interested
1286 * in have the keys; Class, BaseClass & Module indicating the module
1287 * ( value ) is either a Class Module, Form Module or a plain VB Module. */
1288 SvStorageStreamRef xProject
= xMacros
->OpenSotStream(
1289 String( RTL_CONSTASCII_USTRINGPARAM( "PROJECT" ) ) );
1291 SvStorageStream
* pStp
= xProject
;
1293 static const String
sThisDoc( RTL_CONSTASCII_USTRINGPARAM( "ThisDocument" ) );
1294 static const String
sModule( RTL_CONSTASCII_USTRINGPARAM( "Module" ) );
1295 static const String
sClass( RTL_CONSTASCII_USTRINGPARAM( "Class" ) );
1296 static const String
sBaseClass( RTL_CONSTASCII_USTRINGPARAM( "BaseClass" ) );
1297 static const String
sDocument( RTL_CONSTASCII_USTRINGPARAM( "Document" ) );
1298 mhModHash
[ sThisDoc
] = ModuleType::Class
;
1299 while ( pStp
->ReadByteStringLine( tmp
, meCharSet
) )
1301 xub_StrLen index
= tmp
.Search( '=' );
1302 if ( index
!= STRING_NOTFOUND
)
1304 String key
= tmp
.Copy( 0, index
);
1305 String value
= tmp
.Copy( index
+ 1 );
1306 if ( key
== sClass
)
1308 mhModHash
[ value
] = ModuleType::Class
;
1309 OSL_TRACE("Module %s is of type Class",
1310 ::rtl::OUStringToOString( value
,
1311 RTL_TEXTENCODING_ASCII_US
).pData
->buffer
);
1313 else if ( key
== sBaseClass
)
1315 mhModHash
[ value
] = ModuleType::Form
;
1316 OSL_TRACE("Module %s is of type Form",
1317 ::rtl::OUStringToOString( value
,
1318 RTL_TEXTENCODING_ASCII_US
).pData
->buffer
);
1320 else if ( key
== sDocument
)
1322 /* #i37965# DR 2004-12-03: add "Document", used i.e.
1323 in Excel for macros attached to sheet or document. */
1325 // value is of form <name>/&H<identifier>, strip the identifier
1326 value
.Erase( value
.Search( '/' ) );
1328 mhModHash
[ value
] = ModuleType::Document
;
1329 OSL_TRACE("Module %s is of type Document VBA",
1330 ::rtl::OUStringToOString( value
,
1331 RTL_TEXTENCODING_ASCII_US
).pData
->buffer
);
1333 else if ( key
== sModule
)
1335 mhModHash
[ value
] = ModuleType::Normal
;
1336 OSL_TRACE("Module %s is of type Normal VBA",
1337 ::rtl::OUStringToOString( value
,
1338 RTL_TEXTENCODING_ASCII_US
).pData
->buffer
);
1343 /* end test for vba stuff */
1347 const StringArray
&VBA_Impl::Decompress(sal_uInt16 nIndex
, int *pOverflow
)
1349 DBG_ASSERT( nIndex
< nOffsets
, "Index out of range" );
1350 SvStorageStreamRef xVBAStream
;
1351 aVBAStrings
.SetSize(1);
1352 aVBAStrings
.Put(0,new String
);
1354 xVBAStream
= xVBA
->OpenSotStream( pOffsets
[nIndex
].sName
,
1355 STREAM_STD_READ
| STREAM_NOCREATE
);
1359 if( !xVBAStream
.Is() || SVSTREAM_OK
!= xVBAStream
->GetError() )
1361 DBG_WARNING("Not able to open vb module ");
1365 xVBAStream
->SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN
);
1366 DecompressVBA( nIndex
, xVBAStream
);
1368 * if len was too big for a single string set that variable ?
1369 * if ((len > XX) && (pOverflow))
1374 String sTempStringa
;
1376 sTempStringa
= String( RTL_CONSTASCII_USTRINGPARAM( "\x0D" ) );
1378 sTempStringa
= String( RTL_CONSTASCII_USTRINGPARAM( "\x0D\x0A" ) );
1379 String
sTempStringb(sTempStringa
);
1380 sTempStringb
+=sComment
;
1381 for(ULONG i
=0;i
<aVBAStrings
.GetSize();i
++)
1383 aVBAStrings
.Get(i
)->SearchAndReplaceAll(
1384 sTempStringa
,sTempStringb
);
1385 aVBAStrings
.Get(i
)->Insert(sComment
,0);
1393 int VBA_Impl::DecompressVBA( int nIndex
, SvStorageStreamRef
&xVBAStream
)
1395 sal_uInt8 nLeadbyte
;
1397 unsigned int nPos
= 0;
1398 int nLen
, nDistance
, nShift
, nClean
=1;
1400 xVBAStream
->Seek( pOffsets
[ nIndex
].nOffset
+ 3 );
1402 while(xVBAStream
->Read(&nLeadbyte
,1))
1404 for(int nPosition
=0x01;nPosition
< 0x100;nPosition
=nPosition
<<1)
1406 //we see if the leadbyte has flagged this location as a dataunit
1407 //which is actually a token which must be looked up in the history
1408 if (nLeadbyte
& nPosition
)
1410 *xVBAStream
>> nToken
;
1415 //For some reason the division of the token into the length
1416 //field of the data to be inserted, and the distance back into
1417 //the history differs depending on how full the history is
1418 int nPos2
= nPos
% nWINDOWLEN
;
1421 else if (nPos2
<= 0x20)
1423 else if (nPos2
<= 0x40)
1425 else if (nPos2
<= 0x80)
1427 else if (nPos2
<= 0x100)
1429 else if (nPos2
<= 0x200)
1431 else if (nPos2
<= 0x400)
1433 else if (nPos2
<= 0x800)
1440 for(i
=0;i
<nShift
;i
++)
1441 nLen
|= nToken
& (1<<i
);
1445 nDistance
= nToken
>> nShift
;
1447 //read the len of data from the history, wrapping around the
1448 //nWINDOWLEN boundary if necessary data read from the history
1449 //is also copied into the recent part of the history as well.
1450 for (i
= 0; i
< nLen
; i
++)
1453 c
= aHistory
[(nPos
-nDistance
-1) % nWINDOWLEN
];
1454 aHistory
[nPos
% nWINDOWLEN
] = c
;
1460 // special boundary case code, not guarantueed to be correct
1461 // seems to work though, there is something wrong with the
1462 // compression scheme (or maybe a feature) where when the data
1463 // ends on a nWINDOWLEN boundary and the excess bytes in the 8
1464 // dataunit list are discarded, and not interpreted as tokens
1466 if ((nPos
!= 0) && ((nPos
% nWINDOWLEN
) == 0) && (nClean
))
1468 xVBAStream
->SeekRel(2);
1470 Output(nWINDOWLEN
, aHistory
);
1473 //This is the normal case for when the data unit is not a
1474 //token to be looked up, but instead some normal data which
1475 //can be output, and placed in the history.
1476 if (xVBAStream
->Read(&aHistory
[nPos
% nWINDOWLEN
],1))
1484 if (nPos
% nWINDOWLEN
)
1485 Output(nPos
% nWINDOWLEN
,aHistory
);
1489 /* vi:set tabstop=4 shiftwidth=4 expandtab: */