diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sax/source/fastparser/fastparser.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sax/source/fastparser/fastparser.cxx')
-rw-r--r-- | sax/source/fastparser/fastparser.cxx | 1693 |
1 files changed, 1693 insertions, 0 deletions
diff --git a/sax/source/fastparser/fastparser.cxx b/sax/source/fastparser/fastparser.cxx new file mode 100644 index 000000000..250078bc5 --- /dev/null +++ b/sax/source/fastparser/fastparser.cxx @@ -0,0 +1,1693 @@ +/* -*- 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 <sax/fastparser.hxx> +#include <sax/fastattribs.hxx> +#include <utility> +#include <xml2utf.hxx> + +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/xml/sax/FastToken.hpp> +#include <com/sun/star/xml/sax/SAXParseException.hpp> +#include <com/sun/star/xml/sax/XFastContextHandler.hpp> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <osl/conditn.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <salhelper/thread.hxx> +#include <tools/diagnose_ex.h> +#include <o3tl/string_view.hxx> + +#include <queue> +#include <memory> +#include <mutex> +#include <optional> +#include <stack> +#include <string_view> +#include <unordered_map> +#include <vector> +#include <cassert> +#include <cstring> +#include <libxml/parser.h> + +// Inverse of libxml's BAD_CAST. +#define XML_CAST( str ) reinterpret_cast< const char* >( str ) + +using namespace ::osl; +using namespace ::cppu; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::io; +using namespace com::sun::star; +using namespace sax_fastparser; + +static void NormalizeURI( OUString& rName ); + +namespace { + +struct Event; +class FastLocatorImpl; +struct NamespaceDefine; +struct Entity; + +typedef std::unordered_map< OUString, sal_Int32 > NamespaceMap; + +struct EventList +{ + std::vector<Event> maEvents; + bool mbIsAttributesEmpty; +}; + +enum class CallbackType { START_ELEMENT, END_ELEMENT, CHARACTERS, PROCESSING_INSTRUCTION, DONE, EXCEPTION }; + +struct Event +{ + CallbackType maType; + sal_Int32 mnElementToken; + OUString msNamespace; + OUString msElementName; + rtl::Reference< FastAttributeList > mxAttributes; + rtl::Reference< FastAttributeList > mxDeclAttributes; + OUString msChars; +}; + +struct NameWithToken +{ + OUString msName; + sal_Int32 mnToken; + + NameWithToken(OUString sName, sal_Int32 nToken) : + msName(std::move(sName)), mnToken(nToken) {} +}; + +struct SaxContext +{ + Reference< XFastContextHandler > mxContext; + sal_Int32 mnElementToken; + OUString maNamespace; + OUString maElementName; + + SaxContext( sal_Int32 nElementToken, const OUString& aNamespace, const OUString& aElementName ): + mnElementToken(nElementToken) + { + if (nElementToken == FastToken::DONTKNOW) + { + maNamespace = aNamespace; + maElementName = aElementName; + } + } +}; + +struct ParserData +{ + css::uno::Reference< css::xml::sax::XFastDocumentHandler > mxDocumentHandler; + rtl::Reference<FastTokenHandlerBase> mxTokenHandler; + css::uno::Reference< css::xml::sax::XErrorHandler > mxErrorHandler; + css::uno::Reference< css::xml::sax::XFastNamespaceHandler >mxNamespaceHandler; + + ParserData(); +}; + +struct NamespaceDefine +{ + OString maPrefix; + sal_Int32 mnToken; + OUString maNamespaceURL; + + NamespaceDefine( OString aPrefix, sal_Int32 nToken, OUString aNamespaceURL ) + : maPrefix(std::move( aPrefix )), mnToken( nToken ), maNamespaceURL(std::move( aNamespaceURL )) {} + NamespaceDefine() : mnToken(-1) {} +}; + +// Entity binds all information needed for a single file | single call of parseStream +struct Entity : public ParserData +{ + // Amount of work producer sends to consumer in one iteration: + static const size_t mnEventListSize = 1000; + + // unique for each Entity instance: + + // Number of valid events in mxProducedEvents: + size_t mnProducedEventsSize; + std::optional<EventList> mxProducedEvents; + std::queue<EventList> maPendingEvents; + std::queue<EventList> maUsedEvents; + std::mutex maEventProtector; + + static const size_t mnEventLowWater = 4; + static const size_t mnEventHighWater = 8; + osl::Condition maConsumeResume; + osl::Condition maProduceResume; + // Event we use to store data if threading is disabled: + Event maSharedEvent; + + // copied in copy constructor: + + // Allow to disable threading for small documents: + bool mbEnableThreads; + css::xml::sax::InputSource maStructSource; + xmlParserCtxtPtr mpParser; + ::sax_expatwrap::XMLFile2UTFConverter maConverter; + + // Exceptions cannot be thrown through the C-XmlParser (possible + // resource leaks), therefore any exception thrown by a UNO callback + // must be saved somewhere until the C-XmlParser is stopped. + css::uno::Any maSavedException; + std::mutex maSavedExceptionMutex; + void saveException( const Any & e ); + // Thread-safe check if maSavedException has value + bool hasException(); + void throwException( const ::rtl::Reference< FastLocatorImpl > &xDocumentLocator, + bool mbDuringParse ); + + std::stack< NameWithToken, std::vector<NameWithToken> > maNamespaceStack; + /* Context for main thread consuming events. + * startElement() stores the data, which characters() and endElement() uses + */ + std::stack< SaxContext, std::vector<SaxContext> > maContextStack; + // Determines which elements of maNamespaceDefines are valid in current context + std::stack< sal_uInt32, std::vector<sal_uInt32> > maNamespaceCount; + std::vector< NamespaceDefine > maNamespaceDefines; + + explicit Entity( const ParserData& rData ); + Entity( const Entity& rEntity ) = delete; + Entity& operator=( const Entity& rEntity ) = delete; + void startElement( Event const *pEvent ); + void characters( const OUString& sChars ); + void endElement(); + void processingInstruction( const OUString& rTarget, const OUString& rData ); + EventList& getEventList(); + Event& getEvent( CallbackType aType ); +}; + +// Stuff for custom entity names +struct ReplacementPair +{ + OUString name; + OUString replacement; +}; +inline bool operator<(const ReplacementPair& lhs, const ReplacementPair& rhs) +{ + return lhs.name < rhs.name; +} +inline bool operator<(const ReplacementPair& lhs, const char* rhs) +{ + return lhs.name.compareToAscii(rhs) < 0; +} + +} // namespace + +namespace sax_fastparser { + +class FastSaxParserImpl +{ +public: + explicit FastSaxParserImpl(); + ~FastSaxParserImpl(); + +private: + std::vector<ReplacementPair> m_Replacements; + std::vector<xmlEntityPtr> m_TemporalEntities; + +public: + // XFastParser + /// @throws css::xml::sax::SAXException + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void parseStream( const css::xml::sax::InputSource& aInputSource ); + /// @throws css::uno::RuntimeException + void setFastDocumentHandler( const css::uno::Reference< css::xml::sax::XFastDocumentHandler >& Handler ); + /// @throws css::uno::RuntimeException + void setTokenHandler( const css::uno::Reference< css::xml::sax::XFastTokenHandler >& Handler ); + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void registerNamespace( const OUString& NamespaceURL, sal_Int32 NamespaceToken ); + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + OUString const & getNamespaceURL( std::u16string_view rPrefix ); + /// @throws css::uno::RuntimeException + void setErrorHandler( const css::uno::Reference< css::xml::sax::XErrorHandler >& Handler ); + /// @throws css::uno::RuntimeException + void setNamespaceHandler( const css::uno::Reference< css::xml::sax::XFastNamespaceHandler >& Handler); + // Fake DTD file + void setCustomEntityNames( + const ::css::uno::Sequence<::css::beans::Pair<::rtl::OUString, ::rtl::OUString>>& replacements); + + // called by the C callbacks of the expat parser + void callbackStartElement( const xmlChar *localName , const xmlChar* prefix, const xmlChar* URI, + int numNamespaces, const xmlChar** namespaces, int numAttributes, const xmlChar **attributes ); + void callbackEndElement(); + void callbackCharacters( const xmlChar* s, int nLen ); + void callbackProcessingInstruction( const xmlChar *target, const xmlChar *data ); + xmlEntityPtr callbackGetEntity( const xmlChar *name ); + + void pushEntity(const ParserData&, xml::sax::InputSource const&); + void popEntity(); + Entity& getEntity() { return *mpTop; } + void parse(); + void produce( bool bForceFlush = false ); + bool m_bIgnoreMissingNSDecl; + bool m_bDisableThreadedParser; + +private: + bool consume(EventList&); + void deleteUsedEvents(); + void sendPendingCharacters(); + void addUnknownElementWithPrefix(const xmlChar **attributes, int i, rtl::Reference< FastAttributeList > const & xAttributes); + + sal_Int32 GetToken( const xmlChar* pName, sal_Int32 nameLen ); + /// @throws css::xml::sax::SAXException + sal_Int32 GetTokenWithPrefix( const xmlChar* pPrefix, int prefixLen, const xmlChar* pName, int nameLen ); + /// @throws css::xml::sax::SAXException + OUString const & GetNamespaceURL( std::string_view rPrefix ); + sal_Int32 GetNamespaceToken( const OUString& rNamespaceURL ); + sal_Int32 GetTokenWithContextNamespace( sal_Int32 nNamespaceToken, const xmlChar* pName, int nNameLen ); + void DefineNamespace( const OString& rPrefix, const OUString& namespaceURL ); + +private: + std::mutex maMutex; ///< Protecting whole parseStream() execution + ::rtl::Reference< FastLocatorImpl > mxDocumentLocator; + NamespaceMap maNamespaceMap; + + ParserData maData; /// Cached parser configuration for next call of parseStream(). + + Entity *mpTop; /// std::stack::top() is amazingly slow => cache this. + std::stack< Entity > maEntities; /// Entity stack for each call of parseStream(). + std::vector<char> pendingCharacters; /// Data from characters() callback that needs to be sent. +}; + +} // namespace sax_fastparser + +namespace { + +class ParserThread: public salhelper::Thread +{ + FastSaxParserImpl *mpParser; +public: + explicit ParserThread(FastSaxParserImpl *pParser): Thread("Parser"), mpParser(pParser) {} +private: + virtual void execute() override + { + try + { + mpParser->parse(); + } + catch (...) + { + Entity &rEntity = mpParser->getEntity(); + rEntity.getEvent( CallbackType::EXCEPTION ); + mpParser->produce( true ); + } + } +}; + +extern "C" { + +static void call_callbackStartElement(void *userData, const xmlChar *localName , const xmlChar* prefix, const xmlChar* URI, + int numNamespaces, const xmlChar** namespaces, int numAttributes, int /*defaultedAttributes*/, const xmlChar **attributes) +{ + FastSaxParserImpl* pFastParser = static_cast<FastSaxParserImpl*>( userData ); + pFastParser->callbackStartElement( localName, prefix, URI, numNamespaces, namespaces, numAttributes, attributes ); +} + +static void call_callbackEndElement(void *userData, const xmlChar* /*localName*/, const xmlChar* /*prefix*/, const xmlChar* /*URI*/) +{ + FastSaxParserImpl* pFastParser = static_cast<FastSaxParserImpl*>( userData ); + pFastParser->callbackEndElement(); +} + +static void call_callbackCharacters( void *userData , const xmlChar *s , int nLen ) +{ + FastSaxParserImpl* pFastParser = static_cast<FastSaxParserImpl*>( userData ); + pFastParser->callbackCharacters( s, nLen ); +} + +static void call_callbackProcessingInstruction( void *userData, const xmlChar *target, const xmlChar *data ) +{ + FastSaxParserImpl* pFastParser = static_cast<FastSaxParserImpl*>( userData ); + pFastParser->callbackProcessingInstruction( target, data ); +} + +static xmlEntityPtr call_callbackGetEntity( void *userData, const xmlChar *name) +{ + FastSaxParserImpl* pFastParser = static_cast<FastSaxParserImpl*>( userData ); + return pFastParser->callbackGetEntity( name ); +} + +} + +class FastLocatorImpl : public WeakImplHelper< XLocator > +{ +public: + explicit FastLocatorImpl(FastSaxParserImpl *p) : mpParser(p) {} + + void dispose() { mpParser = nullptr; } + /// @throws RuntimeException + void checkDispose() const { if( !mpParser ) throw DisposedException(); } + + //XLocator + 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: + FastSaxParserImpl *mpParser; +}; + +sal_Int32 SAL_CALL FastLocatorImpl::getColumnNumber() +{ + checkDispose(); + return xmlSAX2GetColumnNumber( mpParser->getEntity().mpParser ); +} + +sal_Int32 SAL_CALL FastLocatorImpl::getLineNumber() +{ + checkDispose(); + return xmlSAX2GetLineNumber( mpParser->getEntity().mpParser ); +} + +OUString SAL_CALL FastLocatorImpl::getPublicId() +{ + checkDispose(); + return mpParser->getEntity().maStructSource.sPublicId; +} + +OUString SAL_CALL FastLocatorImpl::getSystemId() +{ + checkDispose(); + return mpParser->getEntity().maStructSource.sSystemId; +} + +ParserData::ParserData() +{} + +Entity::Entity(const ParserData& rData) + : ParserData(rData) + , mnProducedEventsSize(0) + , mbEnableThreads(false) + , mpParser(nullptr) +{ +} + +void Entity::startElement( Event const *pEvent ) +{ + const sal_Int32& nElementToken = pEvent->mnElementToken; + const OUString& aNamespace = pEvent->msNamespace; + const OUString& aElementName = pEvent->msElementName; + + // Use un-wrapped pointers to avoid significant acquire/release overhead + XFastContextHandler *pParentContext = nullptr; + if( !maContextStack.empty() ) + { + pParentContext = maContextStack.top().mxContext.get(); + if( !pParentContext ) + { + maContextStack.push( SaxContext(nElementToken, aNamespace, aElementName) ); + return; + } + } + + maContextStack.push( SaxContext( nElementToken, aNamespace, aElementName ) ); + + try + { + Reference< XFastAttributeList > xAttr( pEvent->mxAttributes ); + Reference< XFastContextHandler > xContext; + + if ( mxNamespaceHandler.is() ) + { + const Sequence< xml::Attribute > NSDeclAttribs = pEvent->mxDeclAttributes->getUnknownAttributes(); + for (const auto& rNSDeclAttrib : NSDeclAttribs) + { + mxNamespaceHandler->registerNamespace( rNSDeclAttrib.Name, rNSDeclAttrib.Value ); + } + } + + if( nElementToken == FastToken::DONTKNOW ) + { + if( pParentContext ) + xContext = pParentContext->createUnknownChildContext( aNamespace, aElementName, xAttr ); + else if( mxDocumentHandler.is() ) + xContext = mxDocumentHandler->createUnknownChildContext( aNamespace, aElementName, xAttr ); + + if( xContext.is() ) + { + xContext->startUnknownElement( aNamespace, aElementName, xAttr ); + } + } + else + { + if( pParentContext ) + xContext = pParentContext->createFastChildContext( nElementToken, xAttr ); + else if( mxDocumentHandler.is() ) + xContext = mxDocumentHandler->createFastChildContext( nElementToken, xAttr ); + + if( xContext.is() ) + xContext->startFastElement( nElementToken, xAttr ); + } + // swap the reference we own in to avoid referencing thrash. + maContextStack.top().mxContext = std::move( xContext ); + } + catch (...) + { + saveException( ::cppu::getCaughtException() ); + } +} + +void Entity::characters( const OUString& sChars ) +{ + if (maContextStack.empty()) + { + // Malformed XML stream !? + return; + } + + XFastContextHandler * pContext( maContextStack.top().mxContext.get() ); + if( pContext ) try + { + pContext->characters( sChars ); + } + catch (...) + { + saveException( ::cppu::getCaughtException() ); + } +} + +void Entity::endElement() +{ + if (maContextStack.empty()) + { + // Malformed XML stream !? + return; + } + + const SaxContext& aContext = maContextStack.top(); + XFastContextHandler* pContext( aContext.mxContext.get() ); + if( pContext ) + try + { + sal_Int32 nElementToken = aContext.mnElementToken; + if( nElementToken != FastToken::DONTKNOW ) + pContext->endFastElement( nElementToken ); + else + pContext->endUnknownElement( aContext.maNamespace, aContext.maElementName ); + } + catch (...) + { + saveException( ::cppu::getCaughtException() ); + } + maContextStack.pop(); +} + +void Entity::processingInstruction( const OUString& rTarget, const OUString& rData ) +{ + if( mxDocumentHandler.is() ) try + { + mxDocumentHandler->processingInstruction( rTarget, rData ); + } + catch (...) + { + saveException( ::cppu::getCaughtException() ); + } +} + +EventList& Entity::getEventList() +{ + if (!mxProducedEvents) + { + std::unique_lock aGuard(maEventProtector); + if (!maUsedEvents.empty()) + { + mxProducedEvents = std::move(maUsedEvents.front()); + maUsedEvents.pop(); + aGuard.unlock(); // unlock + mnProducedEventsSize = 0; + } + if (!mxProducedEvents) + { + mxProducedEvents.emplace(); + mxProducedEvents->maEvents.resize(mnEventListSize); + mxProducedEvents->mbIsAttributesEmpty = false; + mnProducedEventsSize = 0; + } + } + return *mxProducedEvents; +} + +Event& Entity::getEvent( CallbackType aType ) +{ + if (!mbEnableThreads) + return maSharedEvent; + + EventList& rEventList = getEventList(); + if (mnProducedEventsSize == rEventList.maEvents.size()) + { + SAL_WARN_IF(!maSavedException.hasValue(), "sax", + "Event vector should only exceed " << mnEventListSize << + " temporarily while an exception is pending"); + rEventList.maEvents.resize(mnProducedEventsSize + 1); + } + Event& rEvent = rEventList.maEvents[mnProducedEventsSize++]; + rEvent.maType = aType; + return rEvent; +} + +OUString lclGetErrorMessage( xmlParserCtxtPtr ctxt, std::u16string_view sSystemId, sal_Int32 nLine ) +{ + const char* pMessage; + xmlErrorPtr error = xmlCtxtGetLastError( ctxt ); + if( error && error->message ) + pMessage = error->message; + else + pMessage = "unknown error"; + return OUString::Concat("[") + sSystemId + " line " + OUString::number(nLine) + "]: " + + OUString(pMessage, strlen(pMessage), RTL_TEXTENCODING_ASCII_US); +} + +// throw an exception, but avoid callback if +// during a threaded produce +void Entity::throwException( const ::rtl::Reference< FastLocatorImpl > &xDocumentLocator, + bool mbDuringParse ) +{ + // Error during parsing ! + Any savedException; + { + std::scoped_lock g(maSavedExceptionMutex); + if (maSavedException.hasValue()) + { + savedException.setValue(&maSavedException, cppu::UnoType<decltype(maSavedException)>::get()); + } + } + SAXParseException aExcept( + lclGetErrorMessage( mpParser, + xDocumentLocator->getSystemId(), + xDocumentLocator->getLineNumber() ), + Reference< XInterface >(), + savedException, + xDocumentLocator->getPublicId(), + xDocumentLocator->getSystemId(), + xDocumentLocator->getLineNumber(), + xDocumentLocator->getColumnNumber() + ); + + // error handler is set, it may throw the exception + if( !mbDuringParse || !mbEnableThreads ) + { + if (mxErrorHandler.is() ) + mxErrorHandler->fatalError( Any( aExcept ) ); + } + + // error handler has not thrown, but parsing must stop => throw ourselves + throw aExcept; +} + +// In the single threaded case we emit events via our C +// callbacks, so any exception caught must be queued up until +// we can safely re-throw it from our C++ parent of parse() + +// If multi-threaded, we need to push an EXCEPTION event, at +// which point we transfer ownership of maSavedException to +// the consuming thread. +void Entity::saveException( const Any & e ) +{ + // fdo#81214 - allow the parser to run on after an exception, + // unexpectedly some 'startElements' produce a UNO_QUERY_THROW + // for XComponent; and yet expect to continue parsing. + SAL_WARN("sax", "Unexpected exception from XML parser " << exceptionToString(e)); + std::scoped_lock g(maSavedExceptionMutex); + if (maSavedException.hasValue()) + { + SAL_INFO("sax.fastparser", "discarding exception, already have one"); + } + else + { + maSavedException = e; + } +} + +bool Entity::hasException() +{ + std::scoped_lock g(maSavedExceptionMutex); + return maSavedException.hasValue(); +} + +} // namespace + +namespace sax_fastparser { + +FastSaxParserImpl::FastSaxParserImpl() : + m_bIgnoreMissingNSDecl(false), + m_bDisableThreadedParser(false), + mpTop(nullptr) +{ + mxDocumentLocator.set( new FastLocatorImpl( this ) ); +} + +FastSaxParserImpl::~FastSaxParserImpl() +{ + if( mxDocumentLocator.is() ) + mxDocumentLocator->dispose(); + for (auto& entity : m_TemporalEntities) + { + if (!entity) + continue; + xmlNodePtr pPtr = reinterpret_cast<xmlNodePtr>(entity); + xmlUnlinkNode(pPtr); + xmlFreeNode(pPtr); + } +} + +void FastSaxParserImpl::DefineNamespace( const OString& rPrefix, const OUString& namespaceURL ) +{ + Entity& rEntity = getEntity(); + assert(!rEntity.maNamespaceCount.empty()); // need a context! + + sal_uInt32 nOffset = rEntity.maNamespaceCount.top()++; + if( rEntity.maNamespaceDefines.size() <= nOffset ) + rEntity.maNamespaceDefines.resize( rEntity.maNamespaceDefines.size() + 64 ); + + rEntity.maNamespaceDefines[nOffset] = NamespaceDefine( rPrefix, GetNamespaceToken( namespaceURL ), namespaceURL ); +} + +sal_Int32 FastSaxParserImpl::GetToken( const xmlChar* pName, sal_Int32 nameLen /* = 0 */ ) +{ + return FastTokenHandlerBase::getTokenFromChars( getEntity(). mxTokenHandler.get(), + XML_CAST( pName ), nameLen ); // uses utf-8 +} + +sal_Int32 FastSaxParserImpl::GetTokenWithPrefix( const xmlChar* pPrefix, int nPrefixLen, const xmlChar* pName, int nNameLen ) +{ + sal_Int32 nNamespaceToken = FastToken::DONTKNOW; + + Entity& rEntity = getEntity(); + if (rEntity.maNamespaceCount.empty()) + return nNamespaceToken; + + sal_uInt32 nNamespace = rEntity.maNamespaceCount.top(); + while( nNamespace-- ) + { + const auto & rNamespaceDefine = rEntity.maNamespaceDefines[nNamespace]; + const OString& rPrefix( rNamespaceDefine.maPrefix ); + if( (rPrefix.getLength() == nPrefixLen) && + rtl_str_reverseCompare_WithLength(rPrefix.pData->buffer, rPrefix.pData->length, XML_CAST( pPrefix ), nPrefixLen ) == 0 ) + { + nNamespaceToken = rNamespaceDefine.mnToken; + break; + } + + if( !nNamespace && !m_bIgnoreMissingNSDecl ) + throw SAXException("No namespace defined for " + OUString(XML_CAST(pPrefix), + nPrefixLen, RTL_TEXTENCODING_UTF8), Reference< XInterface >(), Any()); + } + + if( nNamespaceToken != FastToken::DONTKNOW ) + { + sal_Int32 nNameToken = GetToken( pName, nNameLen ); + if( nNameToken != FastToken::DONTKNOW ) + return nNamespaceToken | nNameToken; + } + + return FastToken::DONTKNOW; +} + +sal_Int32 FastSaxParserImpl::GetNamespaceToken( const OUString& rNamespaceURL ) +{ + NamespaceMap::iterator aIter( maNamespaceMap.find( rNamespaceURL ) ); + if( aIter != maNamespaceMap.end() ) + return (*aIter).second; + else + return FastToken::DONTKNOW; +} + +OUString const & FastSaxParserImpl::GetNamespaceURL( std::string_view rPrefix ) +{ + Entity& rEntity = getEntity(); + if( !rEntity.maNamespaceCount.empty() ) + { + sal_uInt32 nNamespace = rEntity.maNamespaceCount.top(); + while( nNamespace-- ) + if( rEntity.maNamespaceDefines[nNamespace].maPrefix == rPrefix ) + return rEntity.maNamespaceDefines[nNamespace].maNamespaceURL; + } + + throw SAXException("No namespace defined for " + OUString::fromUtf8(rPrefix), + Reference< XInterface >(), Any()); +} + +sal_Int32 FastSaxParserImpl::GetTokenWithContextNamespace( sal_Int32 nNamespaceToken, const xmlChar* pName, int nNameLen ) +{ + if( nNamespaceToken != FastToken::DONTKNOW ) + { + sal_Int32 nNameToken = GetToken( pName, nNameLen ); + if( nNameToken != FastToken::DONTKNOW ) + return nNamespaceToken | nNameToken; + } + + return FastToken::DONTKNOW; +} + +namespace +{ + class ParserCleanup + { + private: + FastSaxParserImpl& m_rParser; + Entity& m_rEntity; + rtl::Reference<ParserThread> m_xParser; + public: + ParserCleanup(FastSaxParserImpl& rParser, Entity& rEntity) + : m_rParser(rParser) + , m_rEntity(rEntity) + { + } + ~ParserCleanup() + { + if (m_rEntity.mpParser) + { + if (m_rEntity.mpParser->myDoc) + xmlFreeDoc(m_rEntity.mpParser->myDoc); + xmlFreeParserCtxt(m_rEntity.mpParser); + } + joinThread(); + m_rParser.popEntity(); + } + void setThread(const rtl::Reference<ParserThread> &xParser) + { + m_xParser = xParser; + } + void joinThread() + { + if (m_xParser.is()) + { + rtl::Reference<ParserThread> xToJoin = m_xParser; + m_xParser.clear(); + xToJoin->join(); + } + } + }; +} +/*************** +* +* parseStream does Parser-startup initializations. The FastSaxParser::parse() method does +* the file-specific initialization work. (During a parser run, external files may be opened) +* +****************/ +void FastSaxParserImpl::parseStream(const InputSource& rStructSource) +{ + xmlInitParser(); + + // Only one text at one time + std::unique_lock guard( maMutex ); + + pushEntity(maData, rStructSource); + Entity& rEntity = getEntity(); + ParserCleanup aEnsureFree(*this, rEntity); + + // start the document + if( rEntity.mxDocumentHandler.is() ) + { + rEntity.mxDocumentHandler->setDocumentLocator( mxDocumentLocator ); + rEntity.mxDocumentHandler->startDocument(); + } + +#ifdef EMSCRIPTEN + rEntity.mbEnableThreads = false; +#else + if (!getenv("SAX_DISABLE_THREADS") && !m_bDisableThreadedParser) + { + Reference<css::io::XSeekable> xSeekable(rEntity.maStructSource.aInputStream, UNO_QUERY); + // available() is not __really__ relevant here, but leave it in as a heuristic for non-seekable streams + rEntity.mbEnableThreads = (xSeekable.is() && xSeekable->getLength() > 10000) + || (rEntity.maStructSource.aInputStream->available() > 10000); + } +#endif + + if (rEntity.mbEnableThreads) + { + rtl::Reference<ParserThread> xParser = new ParserThread(this); + xParser->launch(); + aEnsureFree.setThread(xParser); + bool done = false; + do { + rEntity.maConsumeResume.wait(); + rEntity.maConsumeResume.reset(); + + std::unique_lock aGuard(rEntity.maEventProtector); + while (!rEntity.maPendingEvents.empty()) + { + if (rEntity.maPendingEvents.size() <= Entity::mnEventLowWater) + rEntity.maProduceResume.set(); // start producer again + + EventList aEventList = std::move(rEntity.maPendingEvents.front()); + rEntity.maPendingEvents.pop(); + aGuard.unlock(); // unlock + + if (!consume(aEventList)) + done = true; + + aGuard.lock(); // lock + + if ( rEntity.maPendingEvents.size() <= Entity::mnEventLowWater ) + { + aGuard.unlock(); + for (auto& rEvent : aEventList.maEvents) + { + if (rEvent.mxAttributes.is()) + { + rEvent.mxAttributes->clear(); + if( rEntity.mxNamespaceHandler.is() ) + rEvent.mxDeclAttributes->clear(); + } + aEventList.mbIsAttributesEmpty = true; + } + aGuard.lock(); + } + + rEntity.maUsedEvents.push(std::move(aEventList)); + } + } while (!done); + aEnsureFree.joinThread(); + deleteUsedEvents(); + + // callbacks used inside XML_Parse may have caught an exception + // No need to lock maSavedExceptionMutex here because parser + // thread is joined. + if( rEntity.maSavedException.hasValue() ) + rEntity.throwException( mxDocumentLocator, true ); + } + else + { + parse(); + } + + // finish document + if( rEntity.mxDocumentHandler.is() ) + { + rEntity.mxDocumentHandler->endDocument(); + } +} + +void FastSaxParserImpl::setFastDocumentHandler( const Reference< XFastDocumentHandler >& Handler ) +{ + maData.mxDocumentHandler = Handler; +} + +void FastSaxParserImpl::setTokenHandler( const Reference< XFastTokenHandler >& xHandler ) +{ + assert( dynamic_cast< FastTokenHandlerBase *>( xHandler.get() ) && "we expect this handler to be a subclass of FastTokenHandlerBase" ); + maData.mxTokenHandler = dynamic_cast< FastTokenHandlerBase *>( xHandler.get() ); +} + +void FastSaxParserImpl::registerNamespace( const OUString& NamespaceURL, sal_Int32 NamespaceToken ) +{ + if( NamespaceToken < FastToken::NAMESPACE ) + throw IllegalArgumentException("Invalid namespace token " + OUString::number(NamespaceToken), css::uno::Reference<css::uno::XInterface >(), 0); + + if( GetNamespaceToken( NamespaceURL ) == FastToken::DONTKNOW ) + { + maNamespaceMap[ NamespaceURL ] = NamespaceToken; + return; + } + throw IllegalArgumentException("namespace URL is already registered: " + NamespaceURL, css::uno::Reference<css::uno::XInterface >(), 0); +} + +OUString const & FastSaxParserImpl::getNamespaceURL( std::u16string_view rPrefix ) +{ + try + { + return GetNamespaceURL( OUStringToOString( rPrefix, RTL_TEXTENCODING_UTF8 ) ); + } + catch (const Exception&) + { + } + throw IllegalArgumentException(); +} + +void FastSaxParserImpl::setErrorHandler(const Reference< XErrorHandler > & Handler) +{ + maData.mxErrorHandler = Handler; +} + +void FastSaxParserImpl::setNamespaceHandler( const Reference< XFastNamespaceHandler >& Handler ) +{ + maData.mxNamespaceHandler = Handler; +} + +void FastSaxParserImpl::setCustomEntityNames( + const ::css::uno::Sequence<::css::beans::Pair<::rtl::OUString, ::rtl::OUString>>& replacements) +{ + m_Replacements.resize(replacements.size()); + for (size_t i = 0; i < replacements.size(); ++i) + { + m_Replacements[i].name = replacements[i].First; + m_Replacements[i].replacement = replacements[i].Second; + } + if (m_Replacements.size() > 1) + std::sort(m_Replacements.begin(), m_Replacements.end()); +} + +void FastSaxParserImpl::deleteUsedEvents() +{ + Entity& rEntity = getEntity(); + std::unique_lock aGuard(rEntity.maEventProtector); + + while (!rEntity.maUsedEvents.empty()) + { + { // the block makes sure that aEventList is destructed outside the lock + EventList aEventList = std::move(rEntity.maUsedEvents.front()); + rEntity.maUsedEvents.pop(); + + aGuard.unlock(); // unlock + } + + aGuard.lock(); // lock + } +} + +void FastSaxParserImpl::produce( bool bForceFlush ) +{ + Entity& rEntity = getEntity(); + if (!(bForceFlush || + rEntity.mnProducedEventsSize >= Entity::mnEventListSize)) + return; + + std::unique_lock aGuard(rEntity.maEventProtector); + + while (rEntity.maPendingEvents.size() >= Entity::mnEventHighWater) + { // pause parsing for a bit + aGuard.unlock(); // unlock + rEntity.maProduceResume.wait(); + rEntity.maProduceResume.reset(); + aGuard.lock(); // lock + } + + rEntity.maPendingEvents.push(std::move(*rEntity.mxProducedEvents)); + rEntity.mxProducedEvents.reset(); + assert(!rEntity.mxProducedEvents); + + aGuard.unlock(); // unlock + + rEntity.maConsumeResume.set(); +} + +bool FastSaxParserImpl::consume(EventList& rEventList) +{ + Entity& rEntity = getEntity(); + rEventList.mbIsAttributesEmpty = false; + for (auto& rEvent : rEventList.maEvents) + { + switch (rEvent.maType) + { + case CallbackType::START_ELEMENT: + rEntity.startElement( &rEvent ); + break; + case CallbackType::END_ELEMENT: + rEntity.endElement(); + break; + case CallbackType::CHARACTERS: + rEntity.characters( rEvent.msChars ); + break; + case CallbackType::PROCESSING_INSTRUCTION: + rEntity.processingInstruction( + rEvent.msNamespace, rEvent.msElementName ); // ( target, data ) + break; + case CallbackType::DONE: + return false; + case CallbackType::EXCEPTION: + rEntity.throwException( mxDocumentLocator, false ); + [[fallthrough]]; // avoid unreachable code warning with some compilers + default: + assert(false); + return false; + } + } + return true; +} + +void FastSaxParserImpl::pushEntity(const ParserData& rEntityData, + xml::sax::InputSource const& rSource) +{ + if (!rSource.aInputStream.is()) + throw SAXException("No input source", Reference<XInterface>(), Any()); + + maEntities.emplace(rEntityData); + mpTop = &maEntities.top(); + + mpTop->maStructSource = rSource; + + mpTop->maConverter.setInputStream(mpTop->maStructSource.aInputStream); + if (!mpTop->maStructSource.sEncoding.isEmpty()) + { + mpTop->maConverter.setEncoding(OUStringToOString(mpTop->maStructSource.sEncoding, RTL_TEXTENCODING_ASCII_US)); + } +} + +void FastSaxParserImpl::popEntity() +{ + maEntities.pop(); + mpTop = !maEntities.empty() ? &maEntities.top() : nullptr; +} + +// starts parsing with actual parser ! +void FastSaxParserImpl::parse() +{ + const int BUFFER_SIZE = 16 * 1024; + Sequence< sal_Int8 > seqOut( BUFFER_SIZE ); + + Entity& rEntity = getEntity(); + + // set all necessary C-Callbacks + static xmlSAXHandler callbacks; + callbacks.startElementNs = call_callbackStartElement; + callbacks.endElementNs = call_callbackEndElement; + callbacks.characters = call_callbackCharacters; + callbacks.processingInstruction = call_callbackProcessingInstruction; + callbacks.getEntity = call_callbackGetEntity; + callbacks.initialized = XML_SAX2_MAGIC; + int nRead = 0; + do + { + nRead = rEntity.maConverter.readAndConvert( seqOut, BUFFER_SIZE ); + if( nRead <= 0 ) + { + if( rEntity.mpParser != nullptr ) + { + if( xmlParseChunk( rEntity.mpParser, reinterpret_cast<const char*>(seqOut.getConstArray()), 0, 1 ) != XML_ERR_OK ) + rEntity.throwException( mxDocumentLocator, true ); + if (rEntity.hasException()) + rEntity.throwException(mxDocumentLocator, true); + } + break; + } + + bool bContinue = true; + if( rEntity.mpParser == nullptr ) + { + // create parser with proper encoding (needs the first chunk of data) + rEntity.mpParser = xmlCreatePushParserCtxt( &callbacks, this, + reinterpret_cast<const char*>(seqOut.getConstArray()), nRead, nullptr ); + if( !rEntity.mpParser ) + throw SAXException("Couldn't create parser", Reference< XInterface >(), Any() ); + + // Tell libxml2 parser to decode entities in attribute values. + // Also allow XML attribute values which are larger than 10MB, because this used to work + // with expat. + // coverity[unsafe_xml_parse_config] - entity support is required + xmlCtxtUseOptions(rEntity.mpParser, XML_PARSE_NOENT | XML_PARSE_HUGE); + } + else + { + bContinue = xmlParseChunk( rEntity.mpParser, reinterpret_cast<const char*>(seqOut.getConstArray()), nRead, 0 ) + == XML_ERR_OK; + } + + // callbacks used inside XML_Parse may have caught an exception + if (!bContinue) + { + rEntity.throwException( mxDocumentLocator, true ); + } + if (rEntity.hasException()) + { + rEntity.throwException( mxDocumentLocator, true ); + } + } while( nRead > 0 ); + rEntity.getEvent( CallbackType::DONE ); + if( rEntity.mbEnableThreads ) + produce( true ); +} + +// The C-Callbacks +void FastSaxParserImpl::callbackStartElement(const xmlChar *localName , const xmlChar* prefix, const xmlChar* URI, + int numNamespaces, const xmlChar** namespaces, int numAttributes, const xmlChar **attributes) +{ + if (!pendingCharacters.empty()) + sendPendingCharacters(); + Entity& rEntity = getEntity(); + if( rEntity.maNamespaceCount.empty() ) + { + rEntity.maNamespaceCount.push(0); + DefineNamespace( "xml", "http://www.w3.org/XML/1998/namespace"); + } + else + { + rEntity.maNamespaceCount.push( rEntity.maNamespaceCount.top() ); + } + + // create attribute map and process namespace instructions + Event& rEvent = rEntity.getEvent( CallbackType::START_ELEMENT ); + bool bIsAttributesEmpty = false; + if ( rEntity.mbEnableThreads ) + bIsAttributesEmpty = rEntity.getEventList().mbIsAttributesEmpty; + + if (rEvent.mxAttributes.is()) + { + if( !bIsAttributesEmpty ) + rEvent.mxAttributes->clear(); + } + else + rEvent.mxAttributes.set( + new FastAttributeList( rEntity.mxTokenHandler.get() ) ); + + if( rEntity.mxNamespaceHandler.is() ) + { + if (rEvent.mxDeclAttributes.is()) + { + if( !bIsAttributesEmpty ) + rEvent.mxDeclAttributes->clear(); + } + else + rEvent.mxDeclAttributes.set( + new FastAttributeList( rEntity.mxTokenHandler.get() ) ); + } + + OUString sNamespace; + sal_Int32 nNamespaceToken = FastToken::DONTKNOW; + if (!rEntity.maNamespaceStack.empty()) + { + sNamespace = rEntity.maNamespaceStack.top().msName; + nNamespaceToken = rEntity.maNamespaceStack.top().mnToken; + } + + try + { + /* #158414# Each element may define new namespaces, also for attributes. + First, process all namespaces, second, process the attributes after namespaces + have been initialized. */ + + // #158414# first: get namespaces + for (int i = 0; i < numNamespaces * 2; i += 2) + { + // namespaces[] is (prefix/URI) + if( namespaces[ i ] != nullptr ) + { + OString aPrefix( XML_CAST( namespaces[ i ] )); + OUString namespaceURL( XML_CAST( namespaces[ i + 1 ] ), strlen( XML_CAST( namespaces[ i + 1 ] )), RTL_TEXTENCODING_UTF8 ); + NormalizeURI( namespaceURL ); + DefineNamespace(aPrefix, namespaceURL); + if( rEntity.mxNamespaceHandler.is() ) + rEvent.mxDeclAttributes->addUnknown( OString( XML_CAST( namespaces[ i ] ) ), OString( XML_CAST( namespaces[ i + 1 ] ) ) ); + } + else + { + // default namespace + sNamespace = OUString( XML_CAST( namespaces[ i + 1 ] ), strlen( XML_CAST( namespaces[ i + 1 ] )), RTL_TEXTENCODING_UTF8 ); + NormalizeURI( sNamespace ); + nNamespaceToken = GetNamespaceToken( sNamespace ); + if( rEntity.mxNamespaceHandler.is() ) + rEvent.mxDeclAttributes->addUnknown( "", OString( XML_CAST( namespaces[ i + 1 ] ) ) ); + } + } + + if ( rEntity.mxTokenHandler.is() ) + { + // #158414# second: fill attribute list with other attributes + rEvent.mxAttributes->reserve( numAttributes ); + for (int i = 0; i < numAttributes * 5; i += 5) + { + // attributes[] is ( localname / prefix / nsURI / valueBegin / valueEnd ) + if( attributes[ i + 1 ] != nullptr ) + { + sal_Int32 nAttributeToken = GetTokenWithPrefix( attributes[ i + 1 ], strlen( XML_CAST( attributes[ i + 1 ] )), attributes[ i ], strlen( XML_CAST( attributes[ i ] ))); + if( nAttributeToken != FastToken::DONTKNOW ) + rEvent.mxAttributes->add( nAttributeToken, XML_CAST( attributes[ i + 3 ] ), attributes[ i + 4 ] - attributes[ i + 3 ] ); + else + addUnknownElementWithPrefix(attributes, i, rEvent.mxAttributes); + } + else + { + sal_Int32 nAttributeToken = GetToken( attributes[ i ], strlen( XML_CAST( attributes[ i ] ))); + if( nAttributeToken != FastToken::DONTKNOW ) + rEvent.mxAttributes->add( nAttributeToken, XML_CAST( attributes[ i + 3 ] ), attributes[ i + 4 ] - attributes[ i + 3 ] ); + else + { + SAL_WARN("xmloff", "unknown attribute " << XML_CAST( attributes[ i ] ) << "=" << + OString( XML_CAST( attributes[ i + 3 ] ), attributes[ i + 4 ] - attributes[ i + 3 ] )); + rEvent.mxAttributes->addUnknown( XML_CAST( attributes[ i ] ), + OString( XML_CAST( attributes[ i + 3 ] ), attributes[ i + 4 ] - attributes[ i + 3 ] )); + } + } + } + + if( prefix != nullptr ) + rEvent.mnElementToken = GetTokenWithPrefix( prefix, strlen( XML_CAST( prefix )), localName, strlen( XML_CAST( localName ))); + else if( !sNamespace.isEmpty() ) + rEvent.mnElementToken = GetTokenWithContextNamespace( nNamespaceToken, localName, strlen( XML_CAST( localName ))); + else + rEvent.mnElementToken = GetToken( localName, strlen( XML_CAST( localName ))); + } + else + { + for (int i = 0; i < numAttributes * 5; i += 5) + { + if( attributes[ i + 1 ] != nullptr ) + addUnknownElementWithPrefix(attributes, i, rEvent.mxAttributes); + else + rEvent.mxAttributes->addUnknown( XML_CAST( attributes[ i ] ), + OString( XML_CAST( attributes[ i + 3 ] ), attributes[ i + 4 ] - attributes[ i + 3 ] )); + } + + rEvent.mnElementToken = FastToken::DONTKNOW; + } + + if( rEvent.mnElementToken == FastToken::DONTKNOW ) + { + OUString aElementPrefix; + if( prefix != nullptr ) + { + if ( !m_bIgnoreMissingNSDecl || URI != nullptr ) + sNamespace = OUString( XML_CAST( URI ), strlen( XML_CAST( URI )), RTL_TEXTENCODING_UTF8 ); + else + sNamespace.clear(); + nNamespaceToken = GetNamespaceToken( sNamespace ); + aElementPrefix = OUString( XML_CAST( prefix ), strlen( XML_CAST( prefix )), RTL_TEXTENCODING_UTF8 ); + } + OUString aElementLocalName( XML_CAST( localName ), strlen( XML_CAST( localName )), RTL_TEXTENCODING_UTF8 ); + rEvent.msNamespace = sNamespace; + if( aElementPrefix.isEmpty() ) + rEvent.msElementName = std::move(aElementLocalName); + else + rEvent.msElementName = aElementPrefix + ":" + aElementLocalName; + } + else // token is always preferred. + rEvent.msElementName.clear(); + + rEntity.maNamespaceStack.push( NameWithToken(sNamespace, nNamespaceToken) ); + if (rEntity.mbEnableThreads) + produce(); + else + { + SAL_INFO("sax.fastparser", " startElement line " << mxDocumentLocator->getLineNumber() << " column " << mxDocumentLocator->getColumnNumber() << " " << ( prefix ? XML_CAST(prefix) : "(null)" ) << ":" << localName); + rEntity.startElement( &rEvent ); + } + } + catch (...) + { + rEntity.saveException( ::cppu::getCaughtException() ); + } +} + +void FastSaxParserImpl::addUnknownElementWithPrefix(const xmlChar **attributes, int i, rtl::Reference< FastAttributeList > const & xAttributes) +{ + OUString aNamespaceURI; + if ( !m_bIgnoreMissingNSDecl || attributes[i + 2] != nullptr ) + aNamespaceURI = OUString( XML_CAST( attributes[ i + 2 ] ), strlen( XML_CAST( attributes[ i + 2 ] )), RTL_TEXTENCODING_UTF8 ); + const OString& rPrefix = OString( XML_CAST( attributes[ i + 1 ] )); + const OString& rLocalName = OString( XML_CAST( attributes[ i ] )); + OString aQualifiedName = (rPrefix.isEmpty())? rLocalName : rPrefix + ":" + rLocalName; + xAttributes->addUnknown( aNamespaceURI, aQualifiedName, + OString( XML_CAST( attributes[ i + 3 ] ), attributes[ i + 4 ] - attributes[ i + 3 ] )); + // ignore an element that otherwise generates a lot of noise in the logs + SAL_WARN_IF(aQualifiedName != "x14ac:dyDescent", "xmloff", "unknown element " << aQualifiedName << " " << aNamespaceURI); +} + +void FastSaxParserImpl::callbackEndElement() +{ + if (!pendingCharacters.empty()) + sendPendingCharacters(); + Entity& rEntity = getEntity(); + SAL_WARN_IF(rEntity.maNamespaceCount.empty(), "sax", "Empty NamespaceCount"); + if( !rEntity.maNamespaceCount.empty() ) + rEntity.maNamespaceCount.pop(); + + SAL_WARN_IF(rEntity.maNamespaceStack.empty(), "sax", "Empty NamespaceStack"); + if( !rEntity.maNamespaceStack.empty() ) + rEntity.maNamespaceStack.pop(); + + rEntity.getEvent( CallbackType::END_ELEMENT ); + if (rEntity.mbEnableThreads) + produce(); + else + rEntity.endElement(); +} + +void FastSaxParserImpl::callbackCharacters( const xmlChar* s, int nLen ) +{ + // SAX interface allows that the characters callback splits content of one XML node + // (e.g. because there's an entity that needs decoding), however for consumers it's + // simpler FastSaxParser's character callback provides the whole string at once, + // so merge data from possible multiple calls and send them at once (before the element + // ends or another one starts). + // + // We use a std::vector<char> to avoid calling into the OUString constructor more than once when + // we have multiple callbackCharacters() calls that we have to merge, which happens surprisingly + // often in writer documents. + int nOriginalLen = pendingCharacters.size(); + pendingCharacters.resize(nOriginalLen + nLen); + memcpy(pendingCharacters.data() + nOriginalLen, s, nLen); +} + +void FastSaxParserImpl::sendPendingCharacters() +{ + Entity& rEntity = getEntity(); + OUString sChars( pendingCharacters.data(), pendingCharacters.size(), RTL_TEXTENCODING_UTF8 ); + if (rEntity.mbEnableThreads) + { + Event& rEvent = rEntity.getEvent( CallbackType::CHARACTERS ); + rEvent.msChars = std::move(sChars); + produce(); + } + else + rEntity.characters( sChars ); + pendingCharacters.resize(0); +} + +void FastSaxParserImpl::callbackProcessingInstruction( const xmlChar *target, const xmlChar *data ) +{ + if (!pendingCharacters.empty()) + sendPendingCharacters(); + Entity& rEntity = getEntity(); + Event& rEvent = rEntity.getEvent( CallbackType::PROCESSING_INSTRUCTION ); + + // This event is very rare, so no need to waste extra space for this + // Using namespace and element strings to be target and data in that order. + rEvent.msNamespace = OUString( XML_CAST( target ), strlen( XML_CAST( target ) ), RTL_TEXTENCODING_UTF8 ); + if ( data != nullptr ) + rEvent.msElementName = OUString( XML_CAST( data ), strlen( XML_CAST( data ) ), RTL_TEXTENCODING_UTF8 ); + else + rEvent.msElementName.clear(); + + if (rEntity.mbEnableThreads) + produce(); + else + rEntity.processingInstruction( rEvent.msNamespace, rEvent.msElementName ); +} + +xmlEntityPtr FastSaxParserImpl::callbackGetEntity( const xmlChar *name ) +{ + if( !name ) + return xmlGetPredefinedEntity(name); + const char* dname = XML_CAST(name); + int lname = strlen(dname); + if( lname == 0 ) + return xmlGetPredefinedEntity(name); + if (m_Replacements.size() > 0) + { + auto it = std::lower_bound(m_Replacements.begin(), m_Replacements.end(), dname); + if (it != m_Replacements.end() && it->name.compareToAscii(dname) == 0) + { + xmlEntityPtr entpt = xmlNewEntity( + nullptr, name, XML_INTERNAL_GENERAL_ENTITY, nullptr, nullptr, + BAD_CAST(OUStringToOString(it->replacement, RTL_TEXTENCODING_UTF8).getStr())); + m_TemporalEntities.push_back(entpt); + return entpt; + } + } + if( lname < 2 ) + return xmlGetPredefinedEntity(name); + if ( dname[0] == '#' ) + { + sal_uInt32 cval = 0; + if( dname[1] == 'x' || dname[1] == 'X' ) + { + if( lname < 3 ) + return xmlGetPredefinedEntity(name); + cval = static_cast<sal_uInt32>( strtoul( dname + 2, nullptr, 16 ) ); + if( cval == 0 ) + return xmlGetPredefinedEntity(name); + OUString vname( &cval, 1 ); + xmlEntityPtr entpt + = xmlNewEntity(nullptr, name, XML_INTERNAL_GENERAL_ENTITY, nullptr, nullptr, + BAD_CAST(OUStringToOString(vname, RTL_TEXTENCODING_UTF8).getStr())); + m_TemporalEntities.push_back(entpt); + return entpt; + } + else + { + cval = static_cast<sal_uInt32>( strtoul( dname + 2, nullptr, 10 ) ); + if( cval == 0 ) + return xmlGetPredefinedEntity(name); + OUString vname(&cval, 1); + xmlEntityPtr entpt + = xmlNewEntity(nullptr, name, XML_INTERNAL_GENERAL_ENTITY, nullptr, nullptr, + BAD_CAST(OUStringToOString(vname, RTL_TEXTENCODING_UTF8).getStr())); + m_TemporalEntities.push_back(entpt); + return entpt; + } + } + return xmlGetPredefinedEntity(name); +} + +FastSaxParser::FastSaxParser() : mpImpl(new FastSaxParserImpl) {} + +FastSaxParser::~FastSaxParser() +{ +} + +void SAL_CALL +FastSaxParser::initialize(css::uno::Sequence< css::uno::Any > const& rArguments) +{ + if (!rArguments.hasElements()) + return; + + OUString str; + if ( !(rArguments[0] >>= str) ) + throw IllegalArgumentException(); + + if ( str == "IgnoreMissingNSDecl" ) + mpImpl->m_bIgnoreMissingNSDecl = true; + else if ( str == "DoSmeplease" ) + ; //just ignore as this is already immune to billion laughs + else if ( str == "DisableThreadedParser" ) + mpImpl->m_bDisableThreadedParser = true; + else + throw IllegalArgumentException(); + +} + +void FastSaxParser::parseStream( const xml::sax::InputSource& aInputSource ) +{ + mpImpl->parseStream(aInputSource); +} + +void FastSaxParser::setFastDocumentHandler( const uno::Reference<xml::sax::XFastDocumentHandler>& Handler ) +{ + mpImpl->setFastDocumentHandler(Handler); +} + +void FastSaxParser::setTokenHandler( const uno::Reference<xml::sax::XFastTokenHandler>& Handler ) +{ + mpImpl->setTokenHandler(Handler); +} + +void FastSaxParser::registerNamespace( const OUString& NamespaceURL, sal_Int32 NamespaceToken ) +{ + mpImpl->registerNamespace(NamespaceURL, NamespaceToken); +} + +OUString FastSaxParser::getNamespaceURL( const OUString& rPrefix ) +{ + return mpImpl->getNamespaceURL(rPrefix); +} + +void FastSaxParser::setErrorHandler( const uno::Reference< xml::sax::XErrorHandler >& Handler ) +{ + mpImpl->setErrorHandler(Handler); +} + +void FastSaxParser::setEntityResolver( const uno::Reference< xml::sax::XEntityResolver >& ) +{ + // not implemented +} + +void FastSaxParser::setLocale( const lang::Locale& ) +{ + // not implemented +} + +void FastSaxParser::setNamespaceHandler( const uno::Reference< css::xml::sax::XFastNamespaceHandler >& Handler) +{ + mpImpl->setNamespaceHandler(Handler); +} + +OUString FastSaxParser::getImplementationName() +{ + return "com.sun.star.comp.extensions.xml.sax.FastParser"; +} + +void FastSaxParser::setCustomEntityNames( + const ::css::uno::Sequence<::css::beans::Pair<::rtl::OUString, ::rtl::OUString>>& replacements) +{ + mpImpl->setCustomEntityNames(replacements); +} + +sal_Bool FastSaxParser::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence<OUString> FastSaxParser::getSupportedServiceNames() +{ + return { "com.sun.star.xml.sax.FastParser" }; +} + +} // namespace sax_fastparser + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_extensions_xml_sax_FastParser_get_implementation( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new FastSaxParser); +} + +// ---------------------------------------------------------- +// copy of the code in xmloff/source/core/namespace.cxx, which adds namespace aliases +// for various dodgy namespace decls in the wild. + +static bool NormalizeW3URI( OUString& rName ); +static bool NormalizeOasisURN( OUString& rName ); + +static void NormalizeURI( OUString& rName ) +{ + // try OASIS + W3 URI normalization + bool bSuccess = NormalizeOasisURN( rName ); + if( ! bSuccess ) + bSuccess = NormalizeW3URI( rName ); +} + +constexpr OUStringLiteral XML_URI_W3_PREFIX(u"http://www.w3.org/"); +constexpr OUStringLiteral XML_URI_XFORMS_SUFFIX(u"/xforms"); +constexpr OUStringLiteral XML_N_XFORMS_1_0(u"http://www.w3.org/2002/xforms"); +constexpr OUStringLiteral XML_N_SVG(u"http://www.w3.org/2000/svg"); +constexpr OUStringLiteral XML_N_SVG_COMPAT(u"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"); +constexpr OUStringLiteral XML_N_FO(u"http://www.w3.org/1999/XSL/Format"); +constexpr OUStringLiteral XML_N_FO_COMPAT(u"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"); +constexpr OUStringLiteral XML_N_SMIL(u"http://www.w3.org/2001/SMIL20/"); +constexpr OUStringLiteral XML_N_SMIL_OLD(u"http://www.w3.org/2001/SMIL20"); +constexpr OUStringLiteral XML_N_SMIL_COMPAT(u"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0"); +constexpr OUStringLiteral XML_URN_OASIS_NAMES_TC(u"urn:oasis:names:tc"); +constexpr OUStringLiteral XML_XMLNS(u"xmlns"); +constexpr OUStringLiteral XML_OPENDOCUMENT(u"opendocument"); +constexpr OUStringLiteral XML_1_0(u"1.0"); + +static bool NormalizeW3URI( OUString& rName ) +{ + // check if URI matches: + // http://www.w3.org/[0-9]*/[:letter:]* + // (year)/(WG name) + // For the following WG/standards names: + // - xforms + + bool bSuccess = false; + const OUString& sURIPrefix = XML_URI_W3_PREFIX; + if( rName.startsWith( sURIPrefix ) ) + { + const OUString& sURISuffix = XML_URI_XFORMS_SUFFIX ; + sal_Int32 nCompareFrom = rName.getLength() - sURISuffix.getLength(); + if( rName.subView( nCompareFrom ) == sURISuffix ) + { + // found W3 prefix, and xforms suffix + rName = XML_N_XFORMS_1_0; + bSuccess = true; + } + } + return bSuccess; +} + +static bool NormalizeOasisURN( OUString& rName ) +{ + // #i38644# + // we exported the wrong namespace for smil, so we correct this here on load + // for older documents + if( rName == XML_N_SVG ) + { + rName = XML_N_SVG_COMPAT; + return true; + } + else if( rName == XML_N_FO ) + { + rName = XML_N_FO_COMPAT; + return true; + } + else if( rName == XML_N_SMIL || rName == XML_N_SMIL_OLD ) + { + rName = XML_N_SMIL_COMPAT; + return true; + } + + + // Check if URN matches + // :urn:oasis:names:tc:[^:]*:xmlns:[^:]*:1.[^:]* + // |---| |---| |-----| + // TC-Id Sub-Id Version + + sal_Int32 nNameLen = rName.getLength(); + // :urn:oasis:names:tc.* + const OUString& rOasisURN = XML_URN_OASIS_NAMES_TC; + if( !rName.startsWith( rOasisURN ) ) + return false; + + // :urn:oasis:names:tc:.* + sal_Int32 nPos = rOasisURN.getLength(); + if( nPos >= nNameLen || rName[nPos] != ':' ) + return false; + + // :urn:oasis:names:tc:[^:]:.* + sal_Int32 nTCIdStart = nPos+1; + sal_Int32 nTCIdEnd = rName.indexOf( ':', nTCIdStart ); + if( -1 == nTCIdEnd ) + return false; + + // :urn:oasis:names:tc:[^:]:xmlns.* + nPos = nTCIdEnd + 1; + std::u16string_view sTmp( rName.subView( nPos ) ); + const OUString& rXMLNS = XML_XMLNS; + if( !o3tl::starts_with(sTmp, rXMLNS ) ) + return false; + + // :urn:oasis:names:tc:[^:]:xmlns:.* + nPos += rXMLNS.getLength(); + if( nPos >= nNameLen || rName[nPos] != ':' ) + return false; + + // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:.* + nPos = rName.indexOf( ':', nPos+1 ); + if( -1 == nPos ) + return false; + + // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:[^:][^:][^:][^:]* + sal_Int32 nVersionStart = nPos+1; + if( nVersionStart+2 >= nNameLen || + -1 != rName.indexOf( ':', nVersionStart ) ) + return false; + + // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:1\.[^:][^:]* + if( rName[nVersionStart] != '1' || rName[nVersionStart+1] != '.' ) + return false; + + // replace [tcid] with current TCID and version with current version. + + rName = rName.subView( 0, nTCIdStart ) + + XML_OPENDOCUMENT + + rName.subView( nTCIdEnd, nVersionStart-nTCIdEnd ) + + XML_1_0; + + return true; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |