diff options
Diffstat (limited to 'oox/source/core/recordparser.cxx')
-rw-r--r-- | oox/source/core/recordparser.cxx | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/oox/source/core/recordparser.cxx b/oox/source/core/recordparser.cxx new file mode 100644 index 000000000..d6f35faca --- /dev/null +++ b/oox/source/core/recordparser.cxx @@ -0,0 +1,326 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <oox/core/recordparser.hxx> + +#include <vector> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/xml/sax/XLocator.hpp> +#include <cppuhelper/implbase.hxx> +#include <osl/diagnose.h> +#include <oox/core/fragmenthandler.hxx> + +namespace oox::core { + +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; + +namespace prv { + +class Locator : public ::cppu::WeakImplHelper< XLocator > +{ +public: + explicit Locator( RecordParser* pParser ) : mpParser( pParser ) {} + + void dispose(); + /// @throws css::uno::RuntimeException + void checkDispose(); + + // com.sun.star.sax.XLocator interface + + virtual sal_Int32 SAL_CALL getColumnNumber() override; + virtual sal_Int32 SAL_CALL getLineNumber() override; + virtual OUString SAL_CALL getPublicId() override; + virtual OUString SAL_CALL getSystemId() override; + +private: + RecordParser* mpParser; +}; + +void Locator::dispose() +{ + mpParser = nullptr; +} + +void Locator::checkDispose() +{ + if( !mpParser ) + throw DisposedException(); +} + +sal_Int32 SAL_CALL Locator::getColumnNumber() +{ + return -1; +} + +sal_Int32 SAL_CALL Locator::getLineNumber() +{ + return -1; +} + +OUString SAL_CALL Locator::getPublicId() +{ + checkDispose(); + return OUString(); +} + +OUString SAL_CALL Locator::getSystemId() +{ + checkDispose(); + return mpParser->getInputSource().maSystemId; +} + +class ContextStack +{ +public: + explicit ContextStack( FragmentHandlerRef const & xHandler ); + + bool empty() const { return maStack.empty(); } + + sal_Int32 getCurrentRecId() const; + bool hasCurrentEndRecId() const; + ContextHandlerRef getCurrentContext() const; + + void pushContext( const RecordInfo& rRec, const ContextHandlerRef& rxContext ); + void popContext(); + +private: + typedef ::std::pair< RecordInfo, ContextHandlerRef > ContextInfo; + typedef ::std::vector< ContextInfo > ContextInfoVec; + + FragmentHandlerRef mxHandler; + ContextInfoVec maStack; +}; + +ContextStack::ContextStack( FragmentHandlerRef const & xHandler ) : + mxHandler( xHandler ) +{ +} + +sal_Int32 ContextStack::getCurrentRecId() const +{ + return maStack.empty() ? -1 : maStack.back().first.mnStartRecId; +} + +bool ContextStack::hasCurrentEndRecId() const +{ + return !maStack.empty() && (maStack.back().first.mnEndRecId >= 0); +} + +ContextHandlerRef ContextStack::getCurrentContext() const +{ + if( !maStack.empty() ) + return maStack.back().second; + return mxHandler; +} + +void ContextStack::pushContext( const RecordInfo& rRecInfo, const ContextHandlerRef& rxContext ) +{ + OSL_ENSURE( (rRecInfo.mnEndRecId >= 0) || maStack.empty() || hasCurrentEndRecId(), + "ContextStack::pushContext - nested incomplete context record identifiers" ); + maStack.emplace_back( rRecInfo, rxContext ); +} + +void ContextStack::popContext() +{ + OSL_ENSURE( !maStack.empty(), "ContextStack::popContext - no context on stack" ); + if( !maStack.empty() ) + { + ContextInfo& rContextInfo = maStack.back(); + if( rContextInfo.second.is() ) + rContextInfo.second->endRecord( rContextInfo.first.mnStartRecId ); + maStack.pop_back(); + } +} + +} // namespace oox::core::prv + +namespace { + +/** Reads a byte from the passed stream, returns true on success. */ +bool lclReadByte( sal_uInt8& ornByte, BinaryInputStream& rStrm ) +{ + return rStrm.readMemory( &ornByte, 1 ) == 1; +} + +/** Reads a compressed signed 32-bit integer from the passed stream. */ +bool lclReadCompressedInt( sal_Int32& ornValue, BinaryInputStream& rStrm ) +{ + ornValue = 0; + sal_uInt8 nByte; + if( !lclReadByte( nByte, rStrm ) ) return false; + ornValue = nByte & 0x7F; + if( (nByte & 0x80) == 0 ) return true; + if( !lclReadByte( nByte, rStrm ) ) return false; + ornValue |= sal_Int32( nByte & 0x7F ) << 7; + if( (nByte & 0x80) == 0 ) return true; + if( !lclReadByte( nByte, rStrm ) ) return false; + ornValue |= sal_Int32( nByte & 0x7F ) << 14; + if( (nByte & 0x80) == 0 ) return true; + if( !lclReadByte( nByte, rStrm ) ) return false; + ornValue |= sal_Int32( nByte & 0x7F ) << 21; + return true; +} + +bool lclReadRecordHeader( sal_Int32& ornRecId, sal_Int32& ornRecSize, BinaryInputStream& rStrm ) +{ + return + lclReadCompressedInt( ornRecId, rStrm ) && (ornRecId >= 0) && + lclReadCompressedInt( ornRecSize, rStrm ) && (ornRecSize >= 0); +} + +bool lclReadNextRecord( sal_Int32& ornRecId, StreamDataSequence& orData, BinaryInputStream& rStrm ) +{ + sal_Int32 nRecSize = 0; + bool bValid = lclReadRecordHeader( ornRecId, nRecSize, rStrm ); + if( bValid ) + { + orData.realloc( nRecSize ); + bValid = (nRecSize == 0) || (rStrm.readData( orData, nRecSize ) == nRecSize); + } + return bValid; +} + +} // namespace + +RecordParser::RecordParser() +{ + mxLocator.set( new prv::Locator( this ) ); +} + +RecordParser::~RecordParser() +{ + if( mxLocator.is() ) + mxLocator->dispose(); +} + +void RecordParser::setFragmentHandler( const ::rtl::Reference< FragmentHandler >& rxHandler ) +{ + mxHandler = rxHandler; + + // build record infos + maStartMap.clear(); + maEndMap.clear(); + const RecordInfo* pRecs = mxHandler.is() ? mxHandler->getRecordInfos() : nullptr; + OSL_ENSURE( pRecs, "RecordInfoProvider::RecordInfoProvider - missing record list" ); + for( ; pRecs && pRecs->mnStartRecId >= 0; ++pRecs ) + { + maStartMap[ pRecs->mnStartRecId ] = *pRecs; + if( pRecs->mnEndRecId >= 0 ) + maEndMap[ pRecs->mnEndRecId ] = *pRecs; + } +} + +void RecordParser::parseStream( const RecordInputSource& rInputSource ) +{ + maSource = rInputSource; + + if( !maSource.mxInStream || maSource.mxInStream->isEof() ) + throw IOException(); + if( !mxHandler.is() ) + throw SAXException(); + + // start the document + mxHandler->setDocumentLocator( mxLocator ); + mxHandler->startDocument(); + + // parse the stream + mxStack.reset( new prv::ContextStack( mxHandler ) ); + sal_Int32 nRecId = 0; + StreamDataSequence aRecData; + while( lclReadNextRecord( nRecId, aRecData, *maSource.mxInStream ) ) + { + // create record stream object from imported record data + SequenceInputStream aRecStrm( aRecData ); + // try to leave a context, there may be other incomplete contexts on the stack + if( const RecordInfo* pEndRecInfo = getEndRecordInfo( nRecId ) ) + { + // finalize contexts without record identifier for context end + while( !mxStack->empty() && !mxStack->hasCurrentEndRecId() ) + mxStack->popContext(); + // finalize the current context and pop context info from stack + OSL_ENSURE( mxStack->getCurrentRecId() == pEndRecInfo->mnStartRecId, "RecordParser::parseStream - context records mismatch" ); + ContextHandlerRef xCurrContext = mxStack->getCurrentContext(); + if( xCurrContext.is() ) + { + // context end record may contain some data, handle it as simple record + aRecStrm.seekToStart(); + xCurrContext->startRecord( nRecId, aRecStrm ); + xCurrContext->endRecord( nRecId ); + } + mxStack->popContext(); + } + else + { + // end context with incomplete record id, if the same id comes again + if( (mxStack->getCurrentRecId() == nRecId) && !mxStack->hasCurrentEndRecId() ) + mxStack->popContext(); + // try to start a new context + ContextHandlerRef xCurrContext = mxStack->getCurrentContext(); + if( xCurrContext.is() ) + { + aRecStrm.seekToStart(); + xCurrContext = xCurrContext->createRecordContext( nRecId, aRecStrm ); + } + // track all context identifiers on the stack (do not push simple records) + const RecordInfo* pStartRecInfo = getStartRecordInfo( nRecId ); + if( pStartRecInfo ) + mxStack->pushContext( *pStartRecInfo, xCurrContext ); + // import the record + if( xCurrContext.is() ) + { + // import the record + aRecStrm.seekToStart(); + xCurrContext->startRecord( nRecId, aRecStrm ); + // end simple records (context records are finished in ContextStack::popContext) + if( !pStartRecInfo ) + xCurrContext->endRecord( nRecId ); + } + } + } + // close remaining contexts (missing context end records or stream error) + while( !mxStack->empty() ) + mxStack->popContext(); + mxStack.reset(); + + // finish document + mxHandler->endDocument(); + + maSource = RecordInputSource(); +} + +const RecordInfo* RecordParser::getStartRecordInfo( sal_Int32 nRecId ) const +{ + RecordInfoMap::const_iterator aIt = maStartMap.find( nRecId ); + return (aIt == maStartMap.end()) ? nullptr : &aIt->second; +} + +const RecordInfo* RecordParser::getEndRecordInfo( sal_Int32 nRecId ) const +{ + RecordInfoMap::const_iterator aIt = maEndMap.find( nRecId ); + return (aIt == maEndMap.end()) ? nullptr : &aIt->second; +} + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |