1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
22 #include <sal/log.hxx>
23 #include <svl/filerec.hxx>
24 #include <osl/endian.h>
27 /* The following macros extract parts from a sal_uInt32 value.
28 These sal_uInt32 values are written out instead of the individual
29 values to reduce the number of calls.
32 #define SFX_REC_PRE(n) ( ((n) & 0x000000FF) )
33 #define SFX_REC_OFS(n) ( ((n) & 0xFFFFFF00) >> 8 )
34 #define SFX_REC_TYP(n) ( ((n) & 0x000000FF) )
35 #define SFX_REC_VER(n) ( ((n) & 0x0000FF00) >> 8 )
36 #define SFX_REC_TAG(n) ( ((n) & 0xFFFF0000) >> 16 )
38 #define SFX_REC_CONTENT_VER(n) ( ((n) & 0x000000FF) )
39 #define SFX_REC_CONTENT_OFS(n) ( ((n) & 0xFFFFFF00) >> 8 )
42 /* The following macros combine parts to a sal_uInt32 value.
43 This sal_uInt32 value is written instead of the individual values
44 to reduce the number of calls.
47 static void lclWriteMiniHeader(SvStream
*p
, sal_uInt32 nPreTag
, sal_uInt32 nStartPos
, sal_uInt32 nEndPos
)
49 (*p
).WriteUInt32( sal_uInt32(nPreTag
) |
50 sal_uInt32(nEndPos
-nStartPos
-SFX_REC_HEADERSIZE_MINI
) << 8 );
53 static void lclWriteHeader(SvStream
*p
, sal_uInt32 nRecType
, sal_uInt32 nContentTag
, sal_uInt32 nContentVer
)
55 (*p
).WriteUInt32( sal_uInt32(nRecType
) |
56 ( sal_uInt32(nContentVer
) << 8 ) |
57 ( sal_uInt32(nContentTag
) << 16 ) );
60 #define SFX_REC_CONTENT_HEADER(nContentVer,n1StStartPos,nCurStartPos) \
61 ( sal_uInt32(nContentVer) | \
62 sal_uInt32( nCurStartPos - n1StStartPos ) << 8 )
64 /** Close the record; write the header
66 * @param bSeekToEndOfRec
67 * if true (default) the stream is positioned at the end of the record;
68 * if false the stream at the start of the content (so after the header).
70 * This method closes the record. The main function is to write the header.
71 * If the header was written already this method is a no-op.
73 * @return sal_uInt32 != 0: Position im the stream immediately after the record.
74 * If 'bSeekToEndOfRecord==sal_True' this will be equal to the current stream position.
75 * == 0: The header was already written.
77 sal_uInt32
SfxMiniRecordWriter::Close(bool bSeekToEndOfRec
)
79 // The header wasn't written yet?
82 // Write header at the start of the record
83 sal_uInt32 nEndPos
= _pStream
->Tell();
84 _pStream
->Seek( _nStartPos
);
85 lclWriteMiniHeader(_pStream
, _nPreTag
, _nStartPos
, nEndPos
);
87 // seek to the end of the record or stay where we are
88 if ( bSeekToEndOfRec
)
89 _pStream
->Seek( nEndPos
);
91 // the header has been written NOW
96 // Record was closed already
101 Internal method for belatedly processsing a header read externally.
102 If the header corresponds to an End-Of-Record tag, an error
103 code is set on the stream and sal_False is returned.
104 But the stream will not be reset to the record start in case of an error.
106 bool SfxMiniRecordReader::SetHeader_Impl( sal_uInt32 nHeader
)
110 // determine reord end and PreTag from the header
111 _nEofRec
= _pStream
->Tell() + SFX_REC_OFS(nHeader
);
112 _nPreTag
= sal::static_int_cast
< sal_uInt8
>(SFX_REC_PRE(nHeader
));
114 // Errror in cae of End of Record tag
115 if ( _nPreTag
== SFX_REC_PRETAG_EOR
)
117 _pStream
->SetError( ERRCODE_IO_WRONGFORMAT
);
126 * an \a SvStream, which has an \a SfxMiniRecord at the current position
128 * Pre-Tag of the wanted record
130 * This constructor interprets a 'pStream' from the current position
131 * as a continuous sequence of records that should be parsable by
132 * this group of classes. The first record that is an <SfxMiniRecord>
133 * (possibly an extened-Record> that has the PreTag 'nTag' will be opened
134 * and represented by this instance.
136 * If the end of stream is reached or a record with tag
137 * SFX_REC_PRETAG_EOR is seen before a record with the wanted 'nTag'
138 * tag is found, the created instance is invalid ('IsValid() ==
139 * sal_False'). The ERRCODE_IO_WRONGFORMAT error code will be set on
140 * the stream,and the current position will be unchanged.
142 * If (the wanted tag) 'nTag==SFX_FILEREC_PRETAG_EOR' no attempt is
143 * made to read a record, but 'IsValid()' is set to sal_False immediately.
144 * This gives the possibility to include backward compatible SfxMiniRecords
145 * without 'new' or 'delete'. See <SfxItemSet::Load()>.
149 * This constructor allows for adding new record types in a backward
150 * compatible way by writing out a record with a new tag followed
151 * by one with an old tag. In that case previous versions of the program
152 * that do not recognise the new tag will skip the new record
153 * automatically. This does cause a slight run time inefficiency,
154 * compared just starting reading, but if the first record
155 * is the wanted one the difference is just a comparison of 2 bytes.
158 SfxMiniRecordReader::SfxMiniRecordReader(SvStream
* pStream
, sal_uInt8 nTag
)
161 , _bSkipped(nTag
== SFX_REC_PRETAG_EOR
)
163 // ignore if we are looking for SFX_REC_PRETAG_EOR
170 // remember StartPos to be able to seek back in case of error
171 sal_uInt32 nStartPos
= pStream
->Tell();
173 // look for the matching record
177 SAL_INFO("svl", "SfxFileRec: searching record at " << pStream
->Tell());
179 pStream
->ReadUInt32( nHeader
);
181 // let the base class extract the header data
182 SetHeader_Impl( nHeader
);
184 // handle error, if any
185 if ( pStream
->IsEof() )
186 _nPreTag
= SFX_REC_PRETAG_EOR
;
187 else if ( _nPreTag
== SFX_REC_PRETAG_EOR
)
188 pStream
->SetError( ERRCODE_IO_WRONGFORMAT
);
191 // stop the loop if the right tag is found
192 if ( _nPreTag
== nTag
)
195 // or else skip the record and continue
196 pStream
->Seek( _nEofRec
);
200 // seek back in case of error
201 pStream
->Seek( nStartPos
);
208 * @param nRecordType for sub classes
209 * @param pStream stream to write the record to
210 * @param nContentTag record type
211 * @param nContentVer record version
213 * internal constructor for sub classes
215 SfxSingleRecordWriter::SfxSingleRecordWriter(sal_uInt8 nRecordType
,
217 sal_uInt16 nContentTag
,
218 sal_uInt8 nContentVer
)
219 : SfxMiniRecordWriter( pStream
, SFX_REC_PRETAG_EXT
)
221 // write extend header after the SfxMiniRec
222 lclWriteHeader(pStream
, nRecordType
, nContentTag
, nContentVer
);
227 * Internal method for reading an SfxMultiRecord header, after
228 * the base class has been initialized and its header has been read.
229 * Set an error code on the stream if needed, but don't seek back
232 inline bool SfxSingleRecordReader::ReadHeader_Impl( sal_uInt16 nTypes
)
236 // read header of the base class
237 sal_uInt32 nHeader
=0;
238 _pStream
->ReadUInt32( nHeader
);
239 if ( !SetHeader_Impl( nHeader
) )
244 _pStream
->ReadUInt32( nHeader
);
245 _nRecordVer
= sal::static_int_cast
< sal_uInt8
>(SFX_REC_VER(nHeader
));
246 _nRecordTag
= sal::static_int_cast
< sal_uInt16
>(SFX_REC_TAG(nHeader
));
248 // wrong record type?
249 _nRecordType
= sal::static_int_cast
< sal_uInt8
>(SFX_REC_TYP(nHeader
));
250 bRet
= 0 != ( nTypes
& _nRecordType
);
258 * @param nTypes arithmetic OR of allowed record types
259 * @param nTag record tag to find
261 * Internal method for reading the header of the first record
262 * that has the tag 'nTag', for which then the type should be
263 * one of the types in 'nTypes'.
265 * If such a record is not found an error code is set, the stream
266 * position is seek-ed back and sal_False is returned.
268 bool SfxSingleRecordReader::FindHeader_Impl(sal_uInt16 nTypes
, sal_uInt16 nTag
)
270 // remember StartPos to be able to seek back in case of error
271 sal_uInt32 nStartPos
= _pStream
->Tell();
273 // look for the right record
274 while ( !_pStream
->IsEof() )
278 SAL_INFO("svl", "SfxFileRec: searching record at " << _pStream
->Tell());
279 _pStream
->ReadUInt32( nHeader
);
280 if ( !SetHeader_Impl( nHeader
) )
284 // found extended record?
285 if ( _nPreTag
== SFX_REC_PRETAG_EXT
)
287 // read extended header
288 _pStream
->ReadUInt32( nHeader
);
289 _nRecordTag
= sal::static_int_cast
< sal_uInt16
>(SFX_REC_TAG(nHeader
));
291 // found right record?
292 if ( _nRecordTag
== nTag
)
294 // record type matches as well?
295 _nRecordType
= sal::static_int_cast
< sal_uInt8
>(
296 SFX_REC_TYP(nHeader
));
297 if ( nTypes
& _nRecordType
)
301 // error => abort loop
307 if ( !_pStream
->IsEof() )
308 _pStream
->Seek( _nEofRec
);
311 // set error and seek back
312 _pStream
->SetError( ERRCODE_IO_WRONGFORMAT
);
313 _pStream
->Seek( nStartPos
);
319 * @param nRecordType sub class record type
320 * @param pStream Stream to write the record to
321 * @param nContentTag Content type
322 * @param nContentVer Content version
324 * Internal method for sub classes
326 SfxMultiFixRecordWriter::SfxMultiFixRecordWriter(sal_uInt8 nRecordType
,
328 sal_uInt16 nContentTag
,
329 sal_uInt8 nContentVer
)
330 : SfxSingleRecordWriter( nRecordType
, pStream
, nContentTag
, nContentVer
)
331 , _nContentStartPos(0)
335 // space for own header
336 pStream
->SeekRel( + SFX_REC_HEADERSIZE_MULTI
);
340 * @see SfxMiniRecordWriter
342 sal_uInt32
SfxMultiFixRecordWriter::Close( bool bSeekToEndOfRec
)
344 // Header not written yet?
347 // remember position after header, to be able to seek back to it
348 sal_uInt32 nEndPos
= SfxSingleRecordWriter::Close( false );
350 // write extended header after SfxSingleRecord
351 _pStream
->WriteUInt16( _nContentCount
);
352 _pStream
->WriteUInt32( _nContentSize
);
354 // seek to end of record or stay after the header
355 if ( bSeekToEndOfRec
)
356 _pStream
->Seek(nEndPos
);
360 // Record was closed already
366 * @param nRecordType Record type of the sub class
367 * @param pStream stream to write the record to
368 * @param nRecordTag record base type
369 * @param nRecordVer record base version
371 * Internal constructor for sub classes
373 SfxMultiVarRecordWriter::SfxMultiVarRecordWriter(sal_uInt8 nRecordType
,
375 sal_uInt16 nRecordTag
,
376 sal_uInt8 nRecordVer
)
377 : SfxMultiFixRecordWriter( nRecordType
, pStream
, nRecordTag
, nRecordVer
),
384 * @param pStream, stream to write the record to
385 * @param nRecordTag record base type
386 * @param nRecordVer record base version
388 * Starts an SfxMultiVarRecord in \a pStream, for which the size
389 * of the content does not have to be known or identical;
390 * after streaming a record its size will be calculated.
394 * This method is not inline since too much code was generated
395 * for initializing the <SvULong> members.
397 SfxMultiVarRecordWriter::SfxMultiVarRecordWriter(SvStream
* pStream
,
398 sal_uInt16 nRecordTag
,
399 sal_uInt8 nRecordVer
)
400 : SfxMultiFixRecordWriter( SFX_REC_TYPE_VARSIZE
,
401 pStream
, nRecordTag
, nRecordVer
),
409 * The destructor of class <SfxMultiVarRecordWriter> closes the
410 * record automatically, in case <SfxMultiVarRecordWriter::Close()>
411 * has not been called explicitly yet.
413 SfxMultiVarRecordWriter::~SfxMultiVarRecordWriter()
415 // close if the header has not been written yet
422 * Internal method for finishing individual content
424 void SfxMultiVarRecordWriter::FlushContent_Impl()
426 // record the version and position offset of the current content;
427 // the position offset is relative ot the start position of the
429 assert(_aContentOfs
.size() == static_cast<size_t>(_nContentCount
)-1);
430 _aContentOfs
.resize(_nContentCount
-1);
431 _aContentOfs
.push_back(
432 SFX_REC_CONTENT_HEADER(_nContentVer
,_nStartPos
,_nContentStartPos
));
436 * @see SfxMultiFixRecordWriter
438 void SfxMultiVarRecordWriter::NewContent()
440 // written Content already?
441 if ( _nContentCount
)
445 _nContentStartPos
= _pStream
->Tell();
450 * @see SfxMiniRecordWriter
452 sal_uInt32
SfxMultiVarRecordWriter::Close( bool bSeekToEndOfRec
)
454 // Header not written yet?
457 // finish content if needed
458 if ( _nContentCount
)
461 // write out content offset table
462 sal_uInt32 nContentOfsPos
= _pStream
->Tell();
463 //! (loop without braces)
464 for ( sal_uInt16 n
= 0; n
< _nContentCount
; ++n
)
465 _pStream
->WriteUInt32( _aContentOfs
[n
] );
467 // skip SfxMultiFixRecordWriter::Close()!
468 sal_uInt32 nEndPos
= SfxSingleRecordWriter::Close( false );
471 _pStream
->WriteUInt16( _nContentCount
);
472 if ( SFX_REC_TYPE_VARSIZE_RELOC
== _nPreTag
||
473 SFX_REC_TYPE_MIXTAGS_RELOC
== _nPreTag
)
474 _pStream
->WriteUInt32( static_cast<sal_uInt32
>(nContentOfsPos
- ( _pStream
->Tell() + sizeof(sal_uInt32
) )) );
476 _pStream
->WriteUInt32( nContentOfsPos
);
478 // seek to the end of the record or stay where we are
479 if ( bSeekToEndOfRec
)
480 _pStream
->Seek(nEndPos
);
484 // Record was closed already
490 * @param nContentTag tag for this content type
491 * @param nContentVer content version
493 * With this method new Content is added to a record and
494 * its tag and version are regorded. This method must be called
495 * to start each content, including the first record.
497 void SfxMultiMixRecordWriter::NewContent(sal_uInt16 nContentTag
, sal_uInt8 nContentVer
)
499 // Finish the previous record if necessary
500 if ( _nContentCount
)
503 // Write the content tag, and record the version and starting position
504 _nContentStartPos
= _pStream
->Tell();
506 _pStream
->WriteUInt16( nContentTag
);
507 _nContentVer
= nContentVer
;
512 * Internal method for reading an SfxMultiRecord-Headers, after
513 * the base class has been initialized and its header has been read.
514 * If an error occurs an error code is set on the stream, but
515 * the stream position will not be seek-ed back in that case.
517 bool SfxMultiRecordReader::ReadHeader_Impl()
520 _pStream
->ReadUInt16( _nContentCount
);
521 _pStream
->ReadUInt32( _nContentSize
); // Fix: each on its own, Var|Mix: table position
523 // do we still need to rade a table with Content offsets?
524 if ( _nRecordType
!= SFX_REC_TYPE_FIXSIZE
)
526 // read table from the stream
527 sal_uInt32 nContentPos
= _pStream
->Tell();
528 if ( _nRecordType
== SFX_REC_TYPE_VARSIZE_RELOC
||
529 _nRecordType
== SFX_REC_TYPE_MIXTAGS_RELOC
)
530 _pStream
->SeekRel( + _nContentSize
);
532 _pStream
->Seek( _nContentSize
);
533 const size_t nMaxRecords
= _pStream
->remainingSize() / sizeof(sal_uInt32
);
534 if (_nContentCount
> nMaxRecords
)
536 SAL_WARN("svl", "Parsing error: " << nMaxRecords
<< " max possible entries, but " <<
537 _nContentCount
<< " claimed, truncating");
538 _nContentCount
= nMaxRecords
;
540 _pContentOfs
= new sal_uInt32
[_nContentCount
];
541 memset(_pContentOfs
, 0, _nContentCount
*sizeof(sal_uInt32
));
542 #if defined(OSL_LITENDIAN)
543 _pStream
->Read( _pContentOfs
, sizeof(sal_uInt32
)*_nContentCount
);
545 // (loop without braces)
546 for ( sal_uInt16 n
= 0; n
< _nContentCount
; ++n
)
547 _pStream
->ReadUInt32( _pContentOfs
[n
] );
549 _pStream
->Seek( nContentPos
);
552 // It was possible to read the error if no error is set on the stream
553 return !_pStream
->GetError();
557 SfxMultiRecordReader::SfxMultiRecordReader( SvStream
*pStream
, sal_uInt16 nTag
)
565 // remember position in the stream to be able seek back in case of error
566 _nStartPos
= pStream
->Tell();
568 // look for matching record and initialize base class
569 SfxSingleRecordReader::Construct_Impl( pStream
);
570 if ( SfxSingleRecordReader::FindHeader_Impl( SFX_REC_TYPE_FIXSIZE
|
571 SFX_REC_TYPE_VARSIZE
| SFX_REC_TYPE_VARSIZE_RELOC
|
572 SFX_REC_TYPE_MIXTAGS
| SFX_REC_TYPE_MIXTAGS_RELOC
,
575 // also read own header
576 if ( !ReadHeader_Impl() )
577 // not readable => mark as invalid and reset stream position
578 SetInvalid_Impl( _nStartPos
);
583 SfxMultiRecordReader::~SfxMultiRecordReader()
585 delete[] _pContentOfs
;
590 * Positions the stream at the start of the next Content, or
591 * for the first call at the start of the first Content in the record,
592 * and reads its header if necessary.
594 * @return sal_False if there is no further Content according to
595 * the record header. Even if sal_True is returned an error can
596 * be set on the stream, for instance if the record finished prematurely
599 bool SfxMultiRecordReader::GetContent()
601 // more Content available?
602 if ( _nContentNo
< _nContentCount
)
604 // position the stream at the start of the Content
605 sal_uInt32 nOffset
= _nRecordType
== SFX_REC_TYPE_FIXSIZE
606 ? _nContentNo
* _nContentSize
607 : SFX_REC_CONTENT_OFS(_pContentOfs
[_nContentNo
]);
608 sal_uInt32 nNewPos
= _nStartPos
+ nOffset
;
609 DBG_ASSERT( nNewPos
>= _pStream
->Tell(), "SfxMultiRecordReader::GetContent() - New position before current, to much data red!" );
611 // #99366#: correct stream pos in every case;
612 // the if clause was added by MT a long time ago,
613 // maybe to 'repair' other corrupt documents; but this
614 // gives errors when writing with 5.1 and reading with current
615 // versions, so we decided to remove the if clause (KA-05/17/2002)
616 // if ( nNewPos > _pStream->Tell() )
617 _pStream
->Seek( nNewPos
);
619 // Read Content Header if available
620 if ( _nRecordType
== SFX_REC_TYPE_MIXTAGS
||
621 _nRecordType
== SFX_REC_TYPE_MIXTAGS_RELOC
)
623 _nContentVer
= sal::static_int_cast
< sal_uInt8
>(
624 SFX_REC_CONTENT_VER(_pContentOfs
[_nContentNo
]));
625 _pStream
->ReadUInt16( _nContentTag
);
628 // Increment ContentNo
637 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */