1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 #include "oox/xls/biffinputstream.hxx"
25
26 #include <algorithm>
27 #include <rtl/ustrbuf.hxx>
28
29 namespace oox {
30 namespace xls {
31
32 // ============================================================================
33
34 using ::rtl::OString;
35 using ::rtl::OStringToOUString;
36 using ::rtl::OUString;
37 using ::rtl::OUStringBuffer;
38
39 // ============================================================================
40
41 namespace prv {
42
BiffInputRecordBuffer(BinaryInputStream & rInStrm)43 BiffInputRecordBuffer::BiffInputRecordBuffer( BinaryInputStream& rInStrm ) :
44 mrInStrm( rInStrm ),
45 mpCurrentData( 0 ),
46 mnHeaderPos( -1 ),
47 mnBodyPos( 0 ),
48 mnBufferBodyPos( 0 ),
49 mnNextHeaderPos( 0 ),
50 mnRecId( BIFF_ID_UNKNOWN ),
51 mnRecSize( 0 ),
52 mnRecPos( 0 ),
53 mbValidHeader( false )
54 {
55 OSL_ENSURE( mrInStrm.isSeekable(), "BiffInputRecordBuffer::BiffInputRecordBuffer - stream must be seekable" );
56 mrInStrm.seekToStart();
57 maOriginalData.reserve( SAL_MAX_UINT16 );
58 maDecodedData.reserve( SAL_MAX_UINT16 );
59 enableDecoder( false ); // updates mpCurrentData
60 }
61
restartAt(sal_Int64 nPos)62 void BiffInputRecordBuffer::restartAt( sal_Int64 nPos )
63 {
64 mnHeaderPos = -1;
65 mnBodyPos = mnBufferBodyPos = 0;
66 mnNextHeaderPos = nPos;
67 mnRecId = BIFF_ID_UNKNOWN;
68 mnRecSize = mnRecPos = 0;
69 mbValidHeader = false;
70 }
71
setDecoder(const BiffDecoderRef & rxDecoder)72 void BiffInputRecordBuffer::setDecoder( const BiffDecoderRef& rxDecoder )
73 {
74 mxDecoder = rxDecoder;
75 enableDecoder( true );
76 updateDecoded();
77 }
78
enableDecoder(bool bEnable)79 void BiffInputRecordBuffer::enableDecoder( bool bEnable )
80 {
81 mpCurrentData = (bEnable && mxDecoder.get() && mxDecoder->isValid()) ? &maDecodedData : &maOriginalData;
82 }
83
startRecord(sal_Int64 nHeaderPos)84 bool BiffInputRecordBuffer::startRecord( sal_Int64 nHeaderPos )
85 {
86 mbValidHeader = (0 <= nHeaderPos) && (nHeaderPos + 4 <= mrInStrm.size());
87 if( mbValidHeader )
88 {
89 mnHeaderPos = nHeaderPos;
90 mrInStrm.seek( nHeaderPos );
91 mrInStrm >> mnRecId >> mnRecSize;
92 mnBodyPos = mrInStrm.tell();
93 mnNextHeaderPos = mnBodyPos + mnRecSize;
94 mbValidHeader = !mrInStrm.isEof() && (mnNextHeaderPos <= mrInStrm.size());
95 }
96 if( !mbValidHeader )
97 {
98 mnHeaderPos = mnBodyPos = -1;
99 mnNextHeaderPos = 0;
100 mnRecId = BIFF_ID_UNKNOWN;
101 mnRecSize = 0;
102 }
103 mnRecPos = 0;
104 return mbValidHeader;
105 }
106
startNextRecord()107 bool BiffInputRecordBuffer::startNextRecord()
108 {
109 return startRecord( mnNextHeaderPos );
110 }
111
getNextRecId()112 sal_uInt16 BiffInputRecordBuffer::getNextRecId()
113 {
114 sal_uInt16 nRecId = BIFF_ID_UNKNOWN;
115 if( mbValidHeader && (mnNextHeaderPos + 4 <= mrInStrm.size()) )
116 {
117 mrInStrm.seek( mnNextHeaderPos );
118 mrInStrm >> nRecId;
119 }
120 return nRecId;
121 }
122
read(void * opData,sal_uInt16 nBytes)123 void BiffInputRecordBuffer::read( void* opData, sal_uInt16 nBytes )
124 {
125 updateBuffer();
126 OSL_ENSURE( nBytes > 0, "BiffInputRecordBuffer::read - nothing to read" );
127 OSL_ENSURE( nBytes <= getRecLeft(), "BiffInputRecordBuffer::read - buffer overflow" );
128 memcpy( opData, &(*mpCurrentData)[ mnRecPos ], nBytes );
129 mnRecPos = mnRecPos + nBytes;
130 }
131
skip(sal_uInt16 nBytes)132 void BiffInputRecordBuffer::skip( sal_uInt16 nBytes )
133 {
134 OSL_ENSURE( nBytes > 0, "BiffInputRecordBuffer::skip - nothing to skip" );
135 OSL_ENSURE( nBytes <= getRecLeft(), "BiffInputRecordBuffer::skip - buffer overflow" );
136 mnRecPos = mnRecPos + nBytes;
137 }
138
updateBuffer()139 void BiffInputRecordBuffer::updateBuffer()
140 {
141 OSL_ENSURE( mbValidHeader, "BiffInputRecordBuffer::updateBuffer - invalid access" );
142 if( mnBodyPos != mnBufferBodyPos )
143 {
144 mrInStrm.seek( mnBodyPos );
145 maOriginalData.resize( mnRecSize );
146 if( mnRecSize > 0 )
147 mrInStrm.readMemory( &maOriginalData.front(), static_cast< sal_Int32 >( mnRecSize ) );
148 mnBufferBodyPos = mnBodyPos;
149 updateDecoded();
150 }
151 }
152
updateDecoded()153 void BiffInputRecordBuffer::updateDecoded()
154 {
155 if( mxDecoder.get() && mxDecoder->isValid() )
156 {
157 maDecodedData.resize( mnRecSize );
158 if( mnRecSize > 0 )
159 mxDecoder->decode( &maDecodedData.front(), &maOriginalData.front(), mnBodyPos, mnRecSize );
160 }
161 }
162
163 } // namespace prv
164
165 // ============================================================================
166
BiffInputStream(BinaryInputStream & rInStream,bool bContLookup)167 BiffInputStream::BiffInputStream( BinaryInputStream& rInStream, bool bContLookup ) :
168 BinaryStreamBase( true ),
169 maRecBuffer( rInStream ),
170 mnRecHandle( -1 ),
171 mnRecId( BIFF_ID_UNKNOWN ),
172 mnAltContId( BIFF_ID_UNKNOWN ),
173 mnCurrRecSize( 0 ),
174 mnComplRecSize( 0 ),
175 mbHasComplRec( false ),
176 mbCont( bContLookup )
177 {
178 mbEof = true; // EOF will be true if stream is not inside a record
179 }
180
181 // record control -------------------------------------------------------------
182
startNextRecord()183 bool BiffInputStream::startNextRecord()
184 {
185 bool bValidRec = false;
186 /* #i4266# ignore zero records (id==len==0) (e.g. the application
187 "Crystal Report" writes zero records between other records) */
188 bool bIsZeroRec = false;
189 do
190 {
191 // record header is never encrypted
192 maRecBuffer.enableDecoder( false );
193 // read header of next raw record, returns false at end of stream
194 bValidRec = maRecBuffer.startNextRecord();
195 // ignore record, if identifier and size are zero
196 bIsZeroRec = (maRecBuffer.getRecId() == 0) && (maRecBuffer.getRecSize() == 0);
197 }
198 while( bValidRec && ((mbCont && isContinueId( maRecBuffer.getRecId() )) || bIsZeroRec) );
199
200 // setup other class members
201 setupRecord();
202 return isInRecord();
203 }
204
startRecordByHandle(sal_Int64 nRecHandle)205 bool BiffInputStream::startRecordByHandle( sal_Int64 nRecHandle )
206 {
207 rewindToRecord( nRecHandle );
208 return startNextRecord();
209 }
210
resetRecord(bool bContLookup,sal_uInt16 nAltContId)211 void BiffInputStream::resetRecord( bool bContLookup, sal_uInt16 nAltContId )
212 {
213 if( isInRecord() )
214 {
215 mbCont = bContLookup;
216 mnAltContId = nAltContId;
217 restartRecord( true );
218 maRecBuffer.enableDecoder( true );
219 }
220 }
221
rewindRecord()222 void BiffInputStream::rewindRecord()
223 {
224 rewindToRecord( mnRecHandle );
225 }
226
227 // decoder --------------------------------------------------------------------
228
setDecoder(const BiffDecoderRef & rxDecoder)229 void BiffInputStream::setDecoder( const BiffDecoderRef& rxDecoder )
230 {
231 maRecBuffer.setDecoder( rxDecoder );
232 }
233
enableDecoder(bool bEnable)234 void BiffInputStream::enableDecoder( bool bEnable )
235 {
236 maRecBuffer.enableDecoder( bEnable );
237 }
238
239 // stream/record state and info -----------------------------------------------
240
getNextRecId()241 sal_uInt16 BiffInputStream::getNextRecId()
242 {
243 sal_uInt16 nRecId = BIFF_ID_UNKNOWN;
244 if( isInRecord() )
245 {
246 sal_Int64 nCurrPos = tell(); // save current position in record
247 while( jumpToNextContinue() ) {} // skip following CONTINUE records
248 if( maRecBuffer.startNextRecord() ) // read header of next record
249 nRecId = maRecBuffer.getRecId();
250 seek( nCurrPos ); // restore position, seek() resets old mbValid state
251 }
252 return nRecId;
253 }
254
255 // BinaryStreamBase interface (seeking) ---------------------------------------
256
size() const257 sal_Int64 BiffInputStream::size() const
258 {
259 if( !mbHasComplRec )
260 const_cast< BiffInputStream* >( this )->calcRecordLength();
261 return mnComplRecSize;
262 }
263
tell() const264 sal_Int64 BiffInputStream::tell() const
265 {
266 return mbEof ? -1 : (mnCurrRecSize - maRecBuffer.getRecLeft());
267 }
268
seek(sal_Int64 nRecPos)269 void BiffInputStream::seek( sal_Int64 nRecPos )
270 {
271 if( isInRecord() )
272 {
273 if( mbEof || (nRecPos < tell()) )
274 restartRecord( false );
275 if( !mbEof && (nRecPos > tell()) )
276 skip( static_cast< sal_Int32 >( nRecPos - tell() ) );
277 }
278 }
279
close()280 void BiffInputStream::close()
281 {
282 }
283
tellBase() const284 sal_Int64 BiffInputStream::tellBase() const
285 {
286 return maRecBuffer.getBaseStream().tell();
287 }
288
sizeBase() const289 sal_Int64 BiffInputStream::sizeBase() const
290 {
291 return maRecBuffer.getBaseStream().size();
292 }
293
294 // BinaryInputStream interface (stream read access) ---------------------------
295
readData(StreamDataSequence & orData,sal_Int32 nBytes,size_t nAtomSize)296 sal_Int32 BiffInputStream::readData( StreamDataSequence& orData, sal_Int32 nBytes, size_t nAtomSize )
297 {
298 sal_Int32 nRet = 0;
299 if( !mbEof )
300 {
301 orData.realloc( ::std::max< sal_Int32 >( nBytes, 0 ) );
302 if( nBytes > 0 )
303 nRet = readMemory( orData.getArray(), nBytes, nAtomSize );
304 }
305 return nRet;
306 }
307
readMemory(void * opMem,sal_Int32 nBytes,size_t nAtomSize)308 sal_Int32 BiffInputStream::readMemory( void* opMem, sal_Int32 nBytes, size_t nAtomSize )
309 {
310 sal_Int32 nRet = 0;
311 if( !mbEof && opMem && (nBytes > 0) )
312 {
313 sal_uInt8* pnBuffer = reinterpret_cast< sal_uInt8* >( opMem );
314 sal_Int32 nBytesLeft = nBytes;
315
316 while( !mbEof && (nBytesLeft > 0) )
317 {
318 sal_uInt16 nReadSize = getMaxRawReadSize( nBytesLeft, nAtomSize );
319 // check nReadSize, stream may already be located at end of a raw record
320 if( nReadSize > 0 )
321 {
322 maRecBuffer.read( pnBuffer, nReadSize );
323 nRet += nReadSize;
324 pnBuffer += nReadSize;
325 nBytesLeft -= nReadSize;
326 }
327 if( nBytesLeft > 0 )
328 jumpToNextContinue();
329 OSL_ENSURE( !mbEof, "BiffInputStream::readMemory - record overread" );
330 }
331 }
332 return nRet;
333 }
334
skip(sal_Int32 nBytes,size_t nAtomSize)335 void BiffInputStream::skip( sal_Int32 nBytes, size_t nAtomSize )
336 {
337 sal_Int32 nBytesLeft = nBytes;
338 while( !mbEof && (nBytesLeft > 0) )
339 {
340 sal_uInt16 nSkipSize = getMaxRawReadSize( nBytesLeft, nAtomSize );
341 // check nSkipSize, stream may already be located at end of a raw record
342 if( nSkipSize > 0 )
343 {
344 maRecBuffer.skip( nSkipSize );
345 nBytesLeft -= nSkipSize;
346 }
347 if( nBytesLeft > 0 )
348 jumpToNextContinue();
349 OSL_ENSURE( !mbEof, "BiffInputStream::skip - record overread" );
350 }
351 }
352
353 // byte strings ---------------------------------------------------------------
354
readByteString(bool b16BitLen,bool bAllowNulChars)355 OString BiffInputStream::readByteString( bool b16BitLen, bool bAllowNulChars )
356 {
357 sal_Int32 nStrLen = b16BitLen ? readuInt16() : readuInt8();
358 return readCharArray( nStrLen, bAllowNulChars );
359 }
360
readByteStringUC(bool b16BitLen,rtl_TextEncoding eTextEnc,bool bAllowNulChars)361 OUString BiffInputStream::readByteStringUC( bool b16BitLen, rtl_TextEncoding eTextEnc, bool bAllowNulChars )
362 {
363 return OStringToOUString( readByteString( b16BitLen, bAllowNulChars ), eTextEnc );
364 }
365
skipByteString(bool b16BitLen)366 void BiffInputStream::skipByteString( bool b16BitLen )
367 {
368 skip( b16BitLen ? readuInt16() : readuInt8() );
369 }
370
371 // Unicode strings ------------------------------------------------------------
372
readUniStringChars(sal_uInt16 nChars,bool b16BitChars,bool bAllowNulChars)373 OUString BiffInputStream::readUniStringChars( sal_uInt16 nChars, bool b16BitChars, bool bAllowNulChars )
374 {
375 OUStringBuffer aBuffer;
376 aBuffer.ensureCapacity( nChars );
377
378 /* This function has to react on CONTINUE records which repeat the flags
379 field in their first byte and may change the 8bit/16bit character mode,
380 thus a plain call to readCompressedUnicodeArray() cannot be used here. */
381 sal_Int32 nCharsLeft = nChars;
382 while( !mbEof && (nCharsLeft > 0) )
383 {
384 /* Read the character array from the remaining part of the current raw
385 record. First, calculate the maximum number of characters that can
386 be read without triggering to start a following CONTINUE record. */
387 sal_Int32 nRawChars = b16BitChars ? (getMaxRawReadSize( nCharsLeft * 2, 2 ) / 2) : getMaxRawReadSize( nCharsLeft, 1 );
388 aBuffer.append( readCompressedUnicodeArray( nRawChars, !b16BitChars, bAllowNulChars ) );
389
390 /* Prepare for next CONTINUE record. Calling jumpToNextStringContinue()
391 reads the leading byte in the following CONTINUE record and updates
392 the b16BitChars flag. */
393 nCharsLeft -= nRawChars;
394 if( nCharsLeft > 0 )
395 jumpToNextStringContinue( b16BitChars );
396 }
397
398 return aBuffer.makeStringAndClear();
399 }
400
readUniStringBody(sal_uInt16 nChars,bool bAllowNulChars)401 OUString BiffInputStream::readUniStringBody( sal_uInt16 nChars, bool bAllowNulChars )
402 {
403 bool b16BitChars;
404 sal_Int32 nAddSize;
405 readUniStringHeader( b16BitChars, nAddSize );
406 OUString aString = readUniStringChars( nChars, b16BitChars, bAllowNulChars );
407 skip( nAddSize );
408 return aString;
409 }
410
readUniString(bool bAllowNulChars)411 OUString BiffInputStream::readUniString( bool bAllowNulChars )
412 {
413 return readUniStringBody( readuInt16(), bAllowNulChars );
414 }
415
skipUniStringChars(sal_uInt16 nChars,bool b16BitChars)416 void BiffInputStream::skipUniStringChars( sal_uInt16 nChars, bool b16BitChars )
417 {
418 sal_Int32 nCharsLeft = nChars;
419 while( !mbEof && (nCharsLeft > 0) )
420 {
421 // skip the character array
422 sal_Int32 nSkipSize = b16BitChars ? getMaxRawReadSize( 2 * nCharsLeft, 2 ) : getMaxRawReadSize( nCharsLeft, 1 );
423 skip( nSkipSize );
424
425 // prepare for next CONTINUE record
426 nCharsLeft -= (b16BitChars ? (nSkipSize / 2) : nSkipSize);
427 if( nCharsLeft > 0 )
428 jumpToNextStringContinue( b16BitChars );
429 }
430 }
431
skipUniStringBody(sal_uInt16 nChars)432 void BiffInputStream::skipUniStringBody( sal_uInt16 nChars )
433 {
434 bool b16BitChars;
435 sal_Int32 nAddSize;
436 readUniStringHeader( b16BitChars, nAddSize );
437 skipUniStringChars( nChars, b16BitChars );
438 skip( nAddSize );
439 }
440
skipUniString()441 void BiffInputStream::skipUniString()
442 {
443 skipUniStringBody( readuInt16() );
444 }
445
446 // private --------------------------------------------------------------------
447
setupRecord()448 void BiffInputStream::setupRecord()
449 {
450 // initialize class members
451 mnRecHandle = maRecBuffer.getRecHeaderPos();
452 mnRecId = maRecBuffer.getRecId();
453 mnAltContId = BIFF_ID_UNKNOWN;
454 mnCurrRecSize = mnComplRecSize = maRecBuffer.getRecSize();
455 mbHasComplRec = !mbCont;
456 mbEof = !isInRecord();
457 // enable decoder in new record
458 enableDecoder( true );
459 }
460
restartRecord(bool bInvalidateRecSize)461 void BiffInputStream::restartRecord( bool bInvalidateRecSize )
462 {
463 if( isInRecord() )
464 {
465 maRecBuffer.startRecord( getRecHandle() );
466 mnCurrRecSize = maRecBuffer.getRecSize();
467 if( bInvalidateRecSize )
468 {
469 mnComplRecSize = mnCurrRecSize;
470 mbHasComplRec = !mbCont;
471 }
472 mbEof = false;
473 }
474 }
475
rewindToRecord(sal_Int64 nRecHandle)476 void BiffInputStream::rewindToRecord( sal_Int64 nRecHandle )
477 {
478 if( nRecHandle >= 0 )
479 {
480 maRecBuffer.restartAt( nRecHandle );
481 mnRecHandle = -1;
482 mbEof = true; // as long as the record is not started
483 }
484 }
485
isContinueId(sal_uInt16 nRecId) const486 bool BiffInputStream::isContinueId( sal_uInt16 nRecId ) const
487 {
488 return (nRecId == BIFF_ID_CONT) || (nRecId == mnAltContId);
489 }
490
jumpToNextContinue()491 bool BiffInputStream::jumpToNextContinue()
492 {
493 mbEof = mbEof || !mbCont || !isContinueId( maRecBuffer.getNextRecId() ) || !maRecBuffer.startNextRecord();
494 if( !mbEof )
495 mnCurrRecSize += maRecBuffer.getRecSize();
496 return !mbEof;
497 }
498
jumpToNextStringContinue(bool & rb16BitChars)499 bool BiffInputStream::jumpToNextStringContinue( bool& rb16BitChars )
500 {
501 OSL_ENSURE( maRecBuffer.getRecLeft() == 0, "BiffInputStream::jumpToNextStringContinue - alignment error" );
502
503 if( mbCont && (getRemaining() > 0) )
504 {
505 jumpToNextContinue();
506 }
507 else if( mnRecId == BIFF_ID_CONT )
508 {
509 /* CONTINUE handling is off, but we have started reading in a CONTINUE
510 record -> start next CONTINUE for TXO import. We really start a new
511 record here - no chance to return to string origin. */
512 mbEof = mbEof || (maRecBuffer.getNextRecId() != BIFF_ID_CONT) || !maRecBuffer.startNextRecord();
513 if( !mbEof )
514 setupRecord();
515 }
516
517 // trying to read the flags invalidates stream, if no CONTINUE record has been found
518 sal_uInt8 nFlags;
519 readValue( nFlags );
520 rb16BitChars = getFlag( nFlags, BIFF_STRF_16BIT );
521 return !mbEof;
522 }
523
calcRecordLength()524 void BiffInputStream::calcRecordLength()
525 {
526 sal_Int64 nCurrPos = tell(); // save current position in record
527 while( jumpToNextContinue() ) {} // jumpToNextContinue() adds up mnCurrRecSize
528 mnComplRecSize = mnCurrRecSize;
529 mbHasComplRec = true;
530 seek( nCurrPos ); // restore position, seek() resets old mbValid state
531 }
532
getMaxRawReadSize(sal_Int32 nBytes,size_t nAtomSize) const533 sal_uInt16 BiffInputStream::getMaxRawReadSize( sal_Int32 nBytes, size_t nAtomSize ) const
534 {
535 sal_uInt16 nMaxSize = getLimitedValue< sal_uInt16, sal_Int32 >( nBytes, 0, maRecBuffer.getRecLeft() );
536 if( (0 < nMaxSize) && (nMaxSize < nBytes) && (nAtomSize > 1) )
537 {
538 // check that remaining data in record buffer is a multiple of the passed atom size
539 sal_uInt16 nPadding = static_cast< sal_uInt16 >( nMaxSize % nAtomSize );
540 OSL_ENSURE( nPadding == 0, "BiffInputStream::getMaxRawReadSize - alignment error" );
541 nMaxSize = nMaxSize - nPadding;
542 }
543 return nMaxSize;
544 }
545
readUniStringHeader(bool & orb16BitChars,sal_Int32 & ornAddSize)546 void BiffInputStream::readUniStringHeader( bool& orb16BitChars, sal_Int32& ornAddSize )
547 {
548 sal_uInt8 nFlags = readuInt8();
549 OSL_ENSURE( !getFlag( nFlags, BIFF_STRF_UNKNOWN ), "BiffInputStream::readUniStringHeader - unknown flags" );
550 orb16BitChars = getFlag( nFlags, BIFF_STRF_16BIT );
551 sal_uInt16 nFontCount = getFlag( nFlags, BIFF_STRF_RICH ) ? readuInt16() : 0;
552 sal_Int32 nPhoneticSize = getFlag( nFlags, BIFF_STRF_PHONETIC ) ? readInt32() : 0;
553 ornAddSize = 4 * nFontCount + ::std::max< sal_Int32 >( 0, nPhoneticSize );
554 }
555
556 // ============================================================================
557
BiffInputStreamPos(BiffInputStream & rStrm)558 BiffInputStreamPos::BiffInputStreamPos( BiffInputStream& rStrm ) :
559 mrStrm( rStrm ),
560 mnRecHandle( rStrm.getRecHandle() ),
561 mnRecPos( rStrm.tell() )
562 {
563 }
564
restorePosition()565 bool BiffInputStreamPos::restorePosition()
566 {
567 bool bValidRec = mrStrm.startRecordByHandle( mnRecHandle );
568 if( bValidRec )
569 mrStrm.seek( mnRecPos );
570 return bValidRec && !mrStrm.isEof();
571 }
572
573 // ============================================================================
574
BiffInputStreamPosGuard(BiffInputStream & rStrm)575 BiffInputStreamPosGuard::BiffInputStreamPosGuard( BiffInputStream& rStrm ) :
576 BiffInputStreamPos( rStrm )
577 {
578 }
579
~BiffInputStreamPosGuard()580 BiffInputStreamPosGuard::~BiffInputStreamPosGuard()
581 {
582 restorePosition();
583 }
584
585 // ============================================================================
586
587 } // namespace xls
588 } // namespace oox
589