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 /oox/source/core | |
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 'oox/source/core')
-rw-r--r-- | oox/source/core/binarycodec.cxx | 41 | ||||
-rw-r--r-- | oox/source/core/contexthandler.cxx | 134 | ||||
-rw-r--r-- | oox/source/core/contexthandler2.cxx | 340 | ||||
-rw-r--r-- | oox/source/core/fastparser.cxx | 139 | ||||
-rw-r--r-- | oox/source/core/fasttokenhandler.cxx | 82 | ||||
-rw-r--r-- | oox/source/core/filterbase.cxx | 596 | ||||
-rw-r--r-- | oox/source/core/filterdetect.cxx | 479 | ||||
-rw-r--r-- | oox/source/core/fragmenthandler.cxx | 119 | ||||
-rw-r--r-- | oox/source/core/fragmenthandler2.cxx | 155 | ||||
-rw-r--r-- | oox/source/core/recordparser.cxx | 326 | ||||
-rw-r--r-- | oox/source/core/relations.cxx | 158 | ||||
-rw-r--r-- | oox/source/core/relationshandler.cxx | 95 | ||||
-rw-r--r-- | oox/source/core/xmlfilterbase.cxx | 1235 |
13 files changed, 3899 insertions, 0 deletions
diff --git a/oox/source/core/binarycodec.cxx b/oox/source/core/binarycodec.cxx new file mode 100644 index 000000000..7266e254b --- /dev/null +++ b/oox/source/core/binarycodec.cxx @@ -0,0 +1,41 @@ +/* -*- 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/binarycodec.hxx> + +#include <oox/helper/attributelist.hxx> + +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +namespace oox::core { + +sal_uInt16 CodecHelper::getPasswordHash( const AttributeList& rAttribs, sal_Int32 nElement ) +{ + sal_Int32 nPasswordHash = rAttribs.getIntegerHex( nElement, 0 ); + OSL_ENSURE( (0 <= nPasswordHash) && (nPasswordHash <= SAL_MAX_UINT16), "CodecHelper::getPasswordHash - invalid password hash" ); + return static_cast< sal_uInt16 >( ((0 <= nPasswordHash) && (nPasswordHash <= SAL_MAX_UINT16)) ? nPasswordHash : 0 ); +} + + + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/core/contexthandler.cxx b/oox/source/core/contexthandler.cxx new file mode 100644 index 000000000..80e274c4a --- /dev/null +++ b/oox/source/core/contexthandler.cxx @@ -0,0 +1,134 @@ +/* -*- 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/contexthandler.hxx> + +#include <oox/core/fragmenthandler.hxx> + +namespace oox::core { + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; + +ContextHandler::ContextHandler( const ContextHandler& rParent ) : + ContextHandler_BASE(rParent), + mxBaseData( rParent.mxBaseData ) +{ +} + +ContextHandler::ContextHandler( const FragmentBaseDataRef& rxBaseData ) : + mxBaseData( rxBaseData ) +{ +} + +ContextHandler::~ContextHandler() +{ +} + +XmlFilterBase& ContextHandler::getFilter() const +{ + return mxBaseData->mrFilter; +} + +const Relations& ContextHandler::getRelations() const +{ + return *mxBaseData->mxRelations; +} + +const OUString& ContextHandler::getFragmentPath() const +{ + return mxBaseData->maFragmentPath; +} + +OUString ContextHandler::getFragmentPathFromRelation( const Relation& rRelation ) const +{ + return mxBaseData->mxRelations->getFragmentPathFromRelation( rRelation ); +} + +OUString ContextHandler::getFragmentPathFromRelId( const OUString& rRelId ) const +{ + return mxBaseData->mxRelations->getFragmentPathFromRelId( rRelId ); +} + +OUString ContextHandler::getFragmentPathFromFirstType( std::u16string_view rType ) const +{ + return mxBaseData->mxRelations->getFragmentPathFromFirstType( rType ); +} + +OUString ContextHandler::getFragmentPathFromFirstTypeFromOfficeDoc( std::u16string_view rType ) + const +{ + return mxBaseData->mxRelations->getFragmentPathFromFirstTypeFromOfficeDoc( rType ); +} + +void ContextHandler::implSetLocator( const Reference< XLocator >& rxLocator ) +{ + mxBaseData->mxLocator = rxLocator; +} + +// com.sun.star.xml.sax.XFastContextHandler interface ------------------------- + +void ContextHandler::startFastElement( sal_Int32, const Reference< XFastAttributeList >& ) +{ +} + +void ContextHandler::startUnknownElement( const OUString&, const OUString&, const Reference< XFastAttributeList >& ) +{ +} + +void ContextHandler::endFastElement( sal_Int32 ) +{ +} + +void ContextHandler::endUnknownElement( const OUString&, const OUString& ) +{ +} + +Reference< XFastContextHandler > ContextHandler::createFastChildContext( sal_Int32, const Reference< XFastAttributeList >& ) +{ + return nullptr; +} + +Reference< XFastContextHandler > ContextHandler::createUnknownChildContext( const OUString&, const OUString&, const Reference< XFastAttributeList >& ) +{ + return nullptr; +} + +void ContextHandler::characters( const OUString& ) +{ +} + +// record context interface --------------------------------------------------- + +ContextHandlerRef ContextHandler::createRecordContext( sal_Int32, SequenceInputStream& ) +{ + return nullptr; +} + +void ContextHandler::startRecord( sal_Int32, SequenceInputStream& ) +{ +} + +void ContextHandler::endRecord( sal_Int32 ) +{ +} + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/core/contexthandler2.cxx b/oox/source/core/contexthandler2.cxx new file mode 100644 index 000000000..1613a3d33 --- /dev/null +++ b/oox/source/core/contexthandler2.cxx @@ -0,0 +1,340 @@ +/* -*- 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/contexthandler2.hxx> +#include <oox/core/xmlfilterbase.hxx> +#include <oox/helper/attributelist.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/tokens.hxx> +#include <rtl/ustrbuf.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +namespace oox::core { + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::xml::sax; + +/** Information about a processed element. */ +struct ElementInfo +{ + OUStringBuffer maChars; /// Collected element characters. + sal_Int32 mnElement; /// The element identifier. + bool mbTrimSpaces; /// True = trims leading/trailing spaces from text data. + + explicit ElementInfo() : maChars( 0), mnElement( XML_TOKEN_INVALID ), mbTrimSpaces( false ) {} +}; + +ContextHandler2Helper::ContextHandler2Helper( bool bEnableTrimSpace, XmlFilterBase& rFilter ) : + mxContextStack( std::make_shared<ContextStack>() ), + mnRootStackSize( 0 ), + mbEnableTrimSpace( bEnableTrimSpace ), + mrFilter( rFilter ) +{ + pushElementInfo( XML_ROOT_CONTEXT ); +} + +ContextHandler2Helper::ContextHandler2Helper( const ContextHandler2Helper& rParent ) : + mxContextStack( rParent.mxContextStack ), + mnRootStackSize( rParent.mxContextStack->size() ), + mbEnableTrimSpace( rParent.mbEnableTrimSpace ), + mrFilter(rParent.mrFilter) +{ +} + +ContextHandler2Helper::~ContextHandler2Helper() +{ +} + +sal_Int32 ContextHandler2Helper::getCurrentElementWithMce() const +{ + return mxContextStack->empty() ? XML_ROOT_CONTEXT : mxContextStack->back().mnElement; +} + +sal_Int32 ContextHandler2Helper::getCurrentElement() const +{ + auto It = std::find_if(mxContextStack->rbegin(), mxContextStack->rend(), + [](const ElementInfo& rItem) { return getNamespace(rItem.mnElement) != NMSP_mce; }); + if (It != mxContextStack->rend()) + return It->mnElement; + return XML_ROOT_CONTEXT; +} + +sal_Int32 ContextHandler2Helper::getParentElement( sal_Int32 nCountBack ) const +{ + if( (nCountBack < 0) || (mxContextStack->size() < o3tl::make_unsigned( nCountBack )) ) + return XML_TOKEN_INVALID; + return (mxContextStack->size() == static_cast< size_t >( nCountBack )) ? + XML_ROOT_CONTEXT : (*mxContextStack)[ mxContextStack->size() - nCountBack - 1 ].mnElement; +} + +bool ContextHandler2Helper::isRootElement() const +{ + return mxContextStack->size() == mnRootStackSize + 1; +} + +Reference< XFastContextHandler > ContextHandler2Helper::implCreateChildContext( + sal_Int32 nElement, const Reference< XFastAttributeList >& rxAttribs ) +{ + // #i76091# process collected characters (calls onCharacters() if needed) + processCollectedChars(); + ContextHandlerRef xContext = onCreateContext( nElement, AttributeList( rxAttribs ) ); + return xContext; +} + +void ContextHandler2Helper::implStartElement( sal_Int32 nElement, const Reference< XFastAttributeList >& rxAttribs ) +{ + AttributeList aAttribs( rxAttribs ); + pushElementInfo( nElement ).mbTrimSpaces = aAttribs.getToken( XML_TOKEN( space ), XML_TOKEN_INVALID ) != XML_preserve; + onStartElement( aAttribs ); +} + +void ContextHandler2Helper::implCharacters( std::u16string_view rChars ) +{ + // #i76091# collect characters until new element starts or this element ends + if( !mxContextStack->empty() ) + mxContextStack->back().maChars.append(rChars); +} + +void ContextHandler2Helper::implEndElement( sal_Int32 nElement ) +{ + OSL_ENSURE( getCurrentElementWithMce() == nElement, "ContextHandler2Helper::implEndElement - context stack broken" ); + if( !mxContextStack->empty() ) + { + // #i76091# process collected characters (calls onCharacters() if needed) + processCollectedChars(); + onEndElement(); + popElementInfo(); + } +} + +ContextHandlerRef ContextHandler2Helper::implCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) +{ + return onCreateRecordContext( nRecId, rStrm ); +} + +void ContextHandler2Helper::implStartRecord( sal_Int32 nRecId, SequenceInputStream& rStrm ) +{ + pushElementInfo( nRecId ); + onStartRecord( rStrm ); +} + +void ContextHandler2Helper::implEndRecord( sal_Int32 nRecId ) +{ + OSL_ENSURE( getCurrentElementWithMce() == nRecId, "ContextHandler2Helper::implEndRecord - context stack broken" ); + if( !mxContextStack->empty() ) + { + onEndRecord(); + popElementInfo(); + } +} + +ElementInfo& ContextHandler2Helper::pushElementInfo( sal_Int32 nElement ) +{ + mxContextStack->emplace_back(); + ElementInfo& rInfo = mxContextStack->back(); + rInfo.mnElement = nElement; + return rInfo; +} + +void ContextHandler2Helper::popElementInfo() +{ + OSL_ENSURE( !mxContextStack->empty(), "ContextHandler2Helper::popElementInfo - context stack broken" ); + if( !mxContextStack->empty() ) + mxContextStack->pop_back(); +} + +void ContextHandler2Helper::processCollectedChars() +{ + OSL_ENSURE( !mxContextStack->empty(), "ContextHandler2Helper::processCollectedChars - no context info" ); + if (mxContextStack->empty()) + return; + ElementInfo& rInfo = mxContextStack->back(); + if( !rInfo.maChars.isEmpty() ) + { + OUString aChars = rInfo.maChars.makeStringAndClear(); + if( mbEnableTrimSpace && rInfo.mbTrimSpaces ) + aChars = aChars.trim(); + if( !aChars.isEmpty() ) + onCharacters( aChars ); + } +} + +ContextHandler2::ContextHandler2( ContextHandler2Helper const & rParent ) : + ContextHandler( dynamic_cast< ContextHandler const & >( rParent ) ), + ContextHandler2Helper( rParent ) +{ +} + +ContextHandler2::~ContextHandler2() +{ +} + +// com.sun.star.xml.sax.XFastContextHandler interface ------------------------- + +Reference< XFastContextHandler > SAL_CALL ContextHandler2::createFastChildContext( + sal_Int32 nElement, const Reference< XFastAttributeList >& rxAttribs ) +{ + if( getNamespace( nElement ) == NMSP_mce ) // TODO for checking 'Ignorable' + { + if( prepareMceContext( nElement, AttributeList( rxAttribs ) ) ) + return this; + return nullptr; + } + + return implCreateChildContext( nElement, rxAttribs ); +} + +void SAL_CALL ContextHandler2::startFastElement( + sal_Int32 nElement, const Reference< XFastAttributeList >& rxAttribs ) +{ + implStartElement( nElement, rxAttribs ); +} + +void SAL_CALL ContextHandler2::characters( const OUString& rChars ) +{ + implCharacters( rChars ); +} + +void SAL_CALL ContextHandler2::endFastElement( sal_Int32 nElement ) +{ + implEndElement( nElement ); +} + +bool ContextHandler2Helper::prepareMceContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( nElement ) + { + case MCE_TOKEN( AlternateContent ): + addMCEState( MCE_STATE::Started ); + break; + + case MCE_TOKEN( Choice ): + { + if (isMCEStateEmpty() || getMCEState() != MCE_STATE::Started) + return false; + + OUString aRequires = rAttribs.getString( XML_Requires, "none" ); + + // At this point we can't access namespaces as the correct xml filter + // is long gone. For now let's decide depending on a list of supported + // namespaces like we do in writerfilter + + std::vector<OUString> aSupportedNS = + { + "a14", // Impress needs this to import math formulas. + "p14", + "p15", + "x12ac", + "v" + }; + + Reference<XServiceInfo> xModel(getDocFilter().getModel(), UNO_QUERY); + if (xModel.is() && xModel->supportsService("com.sun.star.sheet.SpreadsheetDocument")) + { + // No a14 for Calc documents, it would cause duplicated shapes as-is. + auto it = std::find(aSupportedNS.begin(), aSupportedNS.end(), "a14"); + if (it != aSupportedNS.end()) + { + aSupportedNS.erase(it); + } + } + + if (std::find(aSupportedNS.begin(), aSupportedNS.end(), aRequires) != aSupportedNS.end()) + setMCEState( MCE_STATE::FoundChoice ) ; + else + return false; + } + break; + + case MCE_TOKEN( Fallback ): + if( !isMCEStateEmpty() && getMCEState() == MCE_STATE::Started ) + break; + return false; + default: + { + OUString str = rAttribs.getString( MCE_TOKEN( Ignorable ), OUString() ); + if( !str.isEmpty() ) + { + // Sequence< css::xml::FastAttribute > attrs = rAttribs.getFastAttributeList()->getFastAttributes(); + // printf("MCE: %s\n", OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() ); + // TODO: Check & Get the namespaces in "Ignorable" + // printf("NS: %d : %s\n", attrs.getLength(), OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + } + return false; + } + return true; +} + +// oox.core.RecordContext interface ------------------------------------------- + +ContextHandlerRef ContextHandler2::createRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) +{ + return implCreateRecordContext( nRecId, rStrm ); +} + +void ContextHandler2::startRecord( sal_Int32 nRecId, SequenceInputStream& rStrm ) +{ + implStartRecord( nRecId, rStrm ); +} + +void ContextHandler2::endRecord( sal_Int32 nRecId ) +{ + implEndRecord( nRecId ); +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef ContextHandler2::onCreateContext( sal_Int32, const AttributeList& ) +{ + return nullptr; +} + +void ContextHandler2::onStartElement( const AttributeList& ) +{ +} + +void ContextHandler2::onCharacters( const OUString& ) +{ +} + +void ContextHandler2::onEndElement() +{ +} + +ContextHandlerRef ContextHandler2::onCreateRecordContext( sal_Int32, SequenceInputStream& ) +{ + return nullptr; +} + +void ContextHandler2::onStartRecord( SequenceInputStream& ) +{ +} + +void ContextHandler2::onEndRecord() +{ +} + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/core/fastparser.cxx b/oox/source/core/fastparser.cxx new file mode 100644 index 000000000..53e5eb78a --- /dev/null +++ b/oox/source/core/fastparser.cxx @@ -0,0 +1,139 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <oox/core/fastparser.hxx> + +#include <oox/core/fasttokenhandler.hxx> +#include <oox/helper/containerhelper.hxx> +#include <oox/helper/storagebase.hxx> +#include <oox/token/namespacemap.hxx> + +#include <sax/fastparser.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 { + +class InputStreamCloseGuard +{ +public: + explicit InputStreamCloseGuard( const Reference< XInputStream >& rxInStream, bool bCloseStream ); + ~InputStreamCloseGuard(); +private: + Reference< XInputStream > mxInStream; + bool mbCloseStream; +}; + +InputStreamCloseGuard::InputStreamCloseGuard( const Reference< XInputStream >& rxInStream, bool bCloseStream ) : + mxInStream( rxInStream ), + mbCloseStream( bCloseStream ) +{ +} + +InputStreamCloseGuard::~InputStreamCloseGuard() +{ + if( mxInStream.is() && mbCloseStream ) try { mxInStream->closeInput(); } catch( Exception& ) {} +} + +} // namespace + +FastParser::FastParser() : + mrNamespaceMap( StaticNamespaceMap() ) +{ + // create a fast parser instance + mxParser = new sax_fastparser::FastSaxParser; + + // create the fast tokenhandler + mxTokenHandler.set( new FastTokenHandler ); + + // create the fast token handler based on the OOXML token list + mxParser->setTokenHandler( mxTokenHandler ); +} + +FastParser::~FastParser() +{ +} + +void FastParser::registerNamespace( sal_Int32 nNamespaceId ) +{ + if( !mxParser.is() ) + throw RuntimeException(); + + // add handling for OOXML strict here + const OUString* pNamespaceUrl = ContainerHelper::getMapElement( mrNamespaceMap.maTransitionalNamespaceMap, nNamespaceId ); + if( !pNamespaceUrl ) + throw IllegalArgumentException(); + + mxParser->registerNamespace( *pNamespaceUrl, nNamespaceId ); + + //also register the OOXML strict namespaces for the same id + const OUString* pNamespaceStrictUrl = ContainerHelper::getMapElement( mrNamespaceMap.maStrictNamespaceMap, nNamespaceId ); + if(pNamespaceStrictUrl && (*pNamespaceUrl != *pNamespaceStrictUrl)) + { + mxParser->registerNamespace( *pNamespaceStrictUrl, nNamespaceId ); + } +} + +void FastParser::setDocumentHandler( const Reference< XFastDocumentHandler >& rxDocHandler ) +{ + if( !mxParser.is() ) + throw RuntimeException(); + mxParser->setFastDocumentHandler( rxDocHandler ); +} + +void FastParser::clearDocumentHandler() +{ + if (!mxParser.is()) + return; + mxParser->setFastDocumentHandler(nullptr); +} + +void FastParser::parseStream( const InputSource& rInputSource, bool bCloseStream ) +{ + // guard closing the input stream also when exceptions are thrown + InputStreamCloseGuard aGuard( rInputSource.aInputStream, bCloseStream ); + if( !mxParser.is() ) + throw RuntimeException(); + mxParser->parseStream( rInputSource ); +} + +void FastParser::parseStream( const Reference< XInputStream >& rxInStream, const OUString& rStreamName ) +{ + InputSource aInputSource; + aInputSource.sSystemId = rStreamName; + aInputSource.aInputStream = rxInStream; + parseStream( aInputSource ); +} + +void FastParser::parseStream( StorageBase& rStorage, const OUString& rStreamName ) +{ + parseStream( rStorage.openInputStream( rStreamName ), rStreamName ); +} + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/core/fasttokenhandler.cxx b/oox/source/core/fasttokenhandler.cxx new file mode 100644 index 000000000..398772df0 --- /dev/null +++ b/oox/source/core/fasttokenhandler.cxx @@ -0,0 +1,82 @@ +/* -*- 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/fasttokenhandler.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <oox/token/tokenmap.hxx> +#include <cppuhelper/supportsservice.hxx> + +using namespace ::com::sun::star; + +namespace oox::core { + +using namespace ::com::sun::star::uno; + +FastTokenHandler::FastTokenHandler() : + mrTokenMap( StaticTokenMap() ) +{ +} + +FastTokenHandler::~FastTokenHandler() +{ +} + +// XServiceInfo +OUString SAL_CALL FastTokenHandler::getImplementationName() +{ + return "com.sun.star.comp.oox.core.FastTokenHandler"; +} + +sal_Bool SAL_CALL FastTokenHandler::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SAL_CALL FastTokenHandler::getSupportedServiceNames() +{ + Sequence<OUString> aServiceNames { "com.sun.star.xml.sax.FastTokenHandler" }; + return aServiceNames; +} + +Sequence< sal_Int8 > FastTokenHandler::getUTF8Identifier( sal_Int32 nToken ) +{ + return mrTokenMap.getUtf8TokenName( nToken ); +} + +sal_Int32 FastTokenHandler::getTokenFromUTF8( const Sequence< sal_Int8 >& rIdentifier ) +{ + return mrTokenMap.getTokenFromUtf8( rIdentifier ); +} + +sal_Int32 FastTokenHandler::getTokenDirect( const char *pToken, sal_Int32 nLength ) const +{ + return mrTokenMap.getTokenFromUTF8( pToken, nLength ); +} + +} // namespace oox::core + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +com_sun_star_comp_oox_core_FastTokenHandler_get_implementation( + uno::XComponentContext* /*pCtx*/, uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new oox::core::FastTokenHandler()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/core/filterbase.cxx b/oox/source/core/filterbase.cxx new file mode 100644 index 000000000..6ae55238c --- /dev/null +++ b/oox/source/core/filterbase.cxx @@ -0,0 +1,596 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/documentconstants.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/scopeguard.hxx> +#include <unotools/mediadescriptor.hxx> +#include <osl/diagnose.h> +#include <rtl/uri.hxx> +#include <memory> +#include <mutex> +#include <set> + +#include <oox/core/filterbase.hxx> +#include <oox/helper/binaryinputstream.hxx> +#include <oox/helper/binaryoutputstream.hxx> +#include <oox/helper/graphichelper.hxx> +#include <oox/helper/modelobjecthelper.hxx> +#include <oox/ole/oleobjecthelper.hxx> +#include <oox/ole/vbaproject.hxx> + +namespace oox::core { + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::graphic; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::uno; + +using ::com::sun::star::container::XNameAccess; +using utl::MediaDescriptor; +using ::comphelper::SequenceAsHashMap; +using ::oox::ole::OleObjectHelper; +using ::oox::ole::VbaProject; + +namespace { + +struct UrlPool +{ + std::mutex maMutex; + ::std::set< OUString > maUrls; +}; + +UrlPool& StaticUrlPool() +{ + static UrlPool SINGLETON; + return SINGLETON; +} + +/** This guard prevents recursive loading/saving of the same document. */ +class DocumentOpenedGuard +{ +public: + explicit DocumentOpenedGuard( const OUString& rUrl ); + ~DocumentOpenedGuard(); + DocumentOpenedGuard(const DocumentOpenedGuard&) = delete; + DocumentOpenedGuard& operator=(const DocumentOpenedGuard&) = delete; + + bool isValid() const { return mbValid; } + +private: + OUString maUrl; + bool mbValid; +}; + +DocumentOpenedGuard::DocumentOpenedGuard( const OUString& rUrl ) +{ + UrlPool& rUrlPool = StaticUrlPool(); + std::scoped_lock aGuard( rUrlPool.maMutex ); + mbValid = rUrl.isEmpty() || (rUrlPool.maUrls.count( rUrl ) == 0); + if( mbValid && !rUrl.isEmpty() ) + { + rUrlPool.maUrls.insert( rUrl ); + maUrl = rUrl; + } +} + +DocumentOpenedGuard::~DocumentOpenedGuard() +{ + UrlPool& rUrlPool = StaticUrlPool(); + std::scoped_lock aGuard( rUrlPool.maMutex ); + if( !maUrl.isEmpty() ) + rUrlPool.maUrls.erase( maUrl ); +} + +/** Specifies whether this filter is an import or export filter. */ +enum FilterDirection +{ + FILTERDIRECTION_UNKNOWN, + FILTERDIRECTION_IMPORT, + FILTERDIRECTION_EXPORT +}; + +} // namespace + +struct FilterBaseImpl +{ + typedef std::shared_ptr< GraphicHelper > GraphicHelperRef; + typedef std::shared_ptr< ModelObjectHelper > ModelObjHelperRef; + typedef std::shared_ptr< OleObjectHelper > OleObjHelperRef; + typedef std::shared_ptr< VbaProject > VbaProjectRef; + + FilterDirection meDirection; + SequenceAsHashMap maArguments; + SequenceAsHashMap maFilterData; + MediaDescriptor maMediaDesc; + OUString maFileUrl; + StorageRef mxStorage; + OoxmlVersion meVersion; + + GraphicHelperRef mxGraphicHelper; /// Graphic and graphic object handling. + ModelObjHelperRef mxModelObjHelper; /// Tables to create new named drawing objects. + std::map<css::uno::Reference<css::lang::XMultiServiceFactory>, ModelObjHelperRef> + mxModelObjHelpers; + OleObjHelperRef mxOleObjHelper; /// OLE object handling. + VbaProjectRef mxVbaProject; /// VBA project manager. + + Reference< XComponentContext > mxComponentContext; + Reference< XModel > mxModel; + Reference< XMultiServiceFactory > mxModelFactory; + Reference< XFrame > mxTargetFrame; + Reference< XInputStream > mxInStream; + Reference< XStream > mxOutStream; + Reference< XStatusIndicator > mxStatusIndicator; + Reference< XInteractionHandler > mxInteractionHandler; + Reference< XShape > mxParentShape; + + bool mbExportVBA; + + bool mbExportTemplate; + + /// @throws RuntimeException + explicit FilterBaseImpl( const Reference< XComponentContext >& rxContext ); + + /// @throws IllegalArgumentException + void setDocumentModel( const Reference< XComponent >& rxComponent ); +}; + +FilterBaseImpl::FilterBaseImpl( const Reference< XComponentContext >& rxContext ) : + meDirection( FILTERDIRECTION_UNKNOWN ), + meVersion( ECMA_DIALECT ), + mxComponentContext( rxContext, UNO_SET_THROW ), + mbExportVBA(false), + mbExportTemplate(false) +{ +} + +void FilterBaseImpl::setDocumentModel( const Reference< XComponent >& rxComponent ) +{ + try + { + mxModel.set( rxComponent, UNO_QUERY_THROW ); + mxModelFactory.set( rxComponent, UNO_QUERY_THROW ); + } + catch( Exception& ) + { + throw IllegalArgumentException(); + } +} + +FilterBase::FilterBase( const Reference< XComponentContext >& rxContext ) : + mxImpl( new FilterBaseImpl( rxContext ) ) +{ +} + +FilterBase::~FilterBase() +{ +} + +bool FilterBase::isImportFilter() const +{ + return mxImpl->meDirection == FILTERDIRECTION_IMPORT; +} + +bool FilterBase::isExportFilter() const +{ + return mxImpl->meDirection == FILTERDIRECTION_EXPORT; +} + +OoxmlVersion FilterBase::getVersion() const +{ + return mxImpl->meVersion; +} + +const Reference< XComponentContext >& FilterBase::getComponentContext() const +{ + return mxImpl->mxComponentContext; +} + +const Reference< XModel >& FilterBase::getModel() const +{ + return mxImpl->mxModel; +} + +const Reference< XMultiServiceFactory >& FilterBase::getModelFactory() const +{ + return mxImpl->mxModelFactory; +} + +const Reference< XFrame >& FilterBase::getTargetFrame() const +{ + return mxImpl->mxTargetFrame; +} + +const Reference< XStatusIndicator >& FilterBase::getStatusIndicator() const +{ + return mxImpl->mxStatusIndicator; +} + +MediaDescriptor& FilterBase::getMediaDescriptor() const +{ + return mxImpl->maMediaDesc; +} + +SequenceAsHashMap& FilterBase::getFilterData() const +{ + return mxImpl->maFilterData; +} + +const OUString& FilterBase::getFileUrl() const +{ + return mxImpl->maFileUrl; +} + +namespace { + +bool lclIsDosDrive( const OUString& rUrl, sal_Int32 nPos = 0 ) +{ + return + (rUrl.getLength() >= nPos + 3) && + ((('A' <= rUrl[ nPos ]) && (rUrl[ nPos ] <= 'Z')) || (('a' <= rUrl[ nPos ]) && (rUrl[ nPos ] <= 'z'))) && + (rUrl[ nPos + 1 ] == ':') && + (rUrl[ nPos + 2 ] == '/'); +} + +} // namespace + +OUString FilterBase::getAbsoluteUrl( const OUString& rUrl ) const +{ + // handle some special cases before calling ::rtl::Uri::convertRelToAbs() + + static const OUStringLiteral aFileSchema = u"file:"; + static const OUStringLiteral aFilePrefix = u"file:///"; + const sal_Int32 nFilePrefixLen = aFilePrefix.getLength(); + static const OUStringLiteral aUncPrefix = u"//"; + + /* (1) convert all backslashes to slashes, and check that passed URL is + not empty. */ + OUString aUrl = rUrl.replace( '\\', '/' ); + if( aUrl.isEmpty() ) + return aUrl; + + /* (2) add 'file:///' to absolute Windows paths, e.g. convert + 'C:/path/file' to 'file:///c:/path/file'. */ + if( lclIsDosDrive( aUrl ) ) + return aFilePrefix + aUrl; + + /* (3) add 'file:' to UNC paths, e.g. convert '//server/path/file' to + 'file://server/path/file'. */ + if( aUrl.match( aUncPrefix ) ) + return aFileSchema + aUrl; + + /* (4) remove additional slashes from UNC paths, e.g. convert + 'file://///server/path/file' to 'file://server/path/file'. */ + if( (aUrl.getLength() >= nFilePrefixLen + 2) && + aUrl.match( aFilePrefix ) && + aUrl.match( aUncPrefix, nFilePrefixLen ) ) + { + return aFileSchema + aUrl.subView( nFilePrefixLen ); + } + + /* (5) handle URLs relative to current drive, e.g. the URL '/path1/file1' + relative to the base URL 'file:///C:/path2/file2' does not result in + the expected 'file:///C:/path1/file1', but in 'file:///path1/file1'. */ + if( aUrl.startsWith("/") && + mxImpl->maFileUrl.match( aFilePrefix ) && + lclIsDosDrive( mxImpl->maFileUrl, nFilePrefixLen ) ) + { + return OUString::Concat(mxImpl->maFileUrl.subView( 0, nFilePrefixLen + 3 )) + aUrl.subView( 1 ); + } + + try + { + return ::rtl::Uri::convertRelToAbs( mxImpl->maFileUrl, aUrl ); + } + catch( ::rtl::MalformedUriException& ) + { + } + return aUrl; +} + +StorageRef const & FilterBase::getStorage() const +{ + return mxImpl->mxStorage; +} + +Reference< XInputStream > FilterBase::openInputStream( const OUString& rStreamName ) const +{ + if (!mxImpl->mxStorage) + throw RuntimeException(); + return mxImpl->mxStorage->openInputStream( rStreamName ); +} + +Reference< XOutputStream > FilterBase::openOutputStream( const OUString& rStreamName ) const +{ + return mxImpl->mxStorage->openOutputStream( rStreamName ); +} + +void FilterBase::commitStorage() const +{ + mxImpl->mxStorage->commit(); +} + +// helpers + +GraphicHelper& FilterBase::getGraphicHelper() const +{ + if( !mxImpl->mxGraphicHelper ) + mxImpl->mxGraphicHelper.reset( implCreateGraphicHelper() ); + return *mxImpl->mxGraphicHelper; +} + +ModelObjectHelper& FilterBase::getModelObjectHelper() const +{ + if( !mxImpl->mxModelObjHelper ) + mxImpl->mxModelObjHelper = std::make_shared<ModelObjectHelper>( mxImpl->mxModelFactory ); + return *mxImpl->mxModelObjHelper; +} + +ModelObjectHelper& FilterBase::getModelObjectHelperForModel( + const css::uno::Reference<css::lang::XMultiServiceFactory>& xFactory) const +{ + if (!mxImpl->mxModelObjHelpers.count(xFactory)) + mxImpl->mxModelObjHelpers[xFactory] = std::make_shared<ModelObjectHelper>(xFactory); + return *mxImpl->mxModelObjHelpers[xFactory]; +} + +OleObjectHelper& FilterBase::getOleObjectHelper() const +{ + if( !mxImpl->mxOleObjHelper ) + mxImpl->mxOleObjHelper = std::make_shared<OleObjectHelper>(mxImpl->mxModelFactory, mxImpl->mxModel); + return *mxImpl->mxOleObjHelper; +} + +VbaProject& FilterBase::getVbaProject() const +{ + if( !mxImpl->mxVbaProject ) + mxImpl->mxVbaProject.reset( implCreateVbaProject() ); + return *mxImpl->mxVbaProject; +} + +bool FilterBase::importBinaryData( StreamDataSequence & orDataSeq, const OUString& rStreamName ) +{ + OSL_ENSURE( !rStreamName.isEmpty(), "FilterBase::importBinaryData - empty stream name" ); + if( rStreamName.isEmpty() ) + return false; + + // try to open the stream (this may fail - do not assert) + BinaryXInputStream aInStrm( openInputStream( rStreamName ), true ); + if( aInStrm.isEof() ) + return false; + + // copy the entire stream to the passed sequence + SequenceOutputStream aOutStrm( orDataSeq ); + aInStrm.copyToStream( aOutStrm ); + return true; +} + +// com.sun.star.lang.XServiceInfo interface + +sal_Bool SAL_CALL FilterBase::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SAL_CALL FilterBase::getSupportedServiceNames() +{ + return { "com.sun.star.document.ImportFilter", "com.sun.star.document.ExportFilter" }; +} + +// com.sun.star.lang.XInitialization interface + +void SAL_CALL FilterBase::initialize( const Sequence< Any >& rArgs ) +{ + if( rArgs.getLength() >= 2 ) try + { + mxImpl->maArguments << rArgs[ 1 ]; + } + catch( Exception& ) + { + } + + if (!rArgs.hasElements()) + return; + + Sequence<css::beans::PropertyValue> aSeq; + rArgs[0] >>= aSeq; + for (const auto& rVal : std::as_const(aSeq)) + { + if (rVal.Name == "UserData") + { + css::uno::Sequence<OUString> aUserDataSeq; + rVal.Value >>= aUserDataSeq; + if (comphelper::findValue(aUserDataSeq, "macro-enabled") != -1) + mxImpl->mbExportVBA = true; + } + else if (rVal.Name == "Flags") + { + sal_Int32 nFlags(0); + rVal.Value >>= nFlags; + mxImpl->mbExportTemplate = bool(static_cast<SfxFilterFlags>(nFlags) & SfxFilterFlags::TEMPLATE); + } + } +} + +// com.sun.star.document.XImporter interface + +void SAL_CALL FilterBase::setTargetDocument( const Reference< XComponent >& rxDocument ) +{ + mxImpl->setDocumentModel( rxDocument ); + mxImpl->meDirection = FILTERDIRECTION_IMPORT; +} + +// com.sun.star.document.XExporter interface + +void SAL_CALL FilterBase::setSourceDocument( const Reference< XComponent >& rxDocument ) +{ + mxImpl->setDocumentModel( rxDocument ); + mxImpl->meDirection = FILTERDIRECTION_EXPORT; +} + +// com.sun.star.document.XFilter interface + +sal_Bool SAL_CALL FilterBase::filter( const Sequence< PropertyValue >& rMediaDescSeq ) +{ + if( !mxImpl->mxModel.is() || !mxImpl->mxModelFactory.is() || (mxImpl->meDirection == FILTERDIRECTION_UNKNOWN) ) + throw RuntimeException(); + + bool bRet = false; + setMediaDescriptor( rMediaDescSeq ); + DocumentOpenedGuard aOpenedGuard( mxImpl->maFileUrl ); + if( aOpenedGuard.isValid() || mxImpl->maFileUrl.isEmpty() ) + { + Reference<XModel> xTempModel = mxImpl->mxModel; + xTempModel->lockControllers(); + comphelper::ScopeGuard const lockControllersGuard([xTempModel]() { + xTempModel->unlockControllers(); + }); + + switch( mxImpl->meDirection ) + { + case FILTERDIRECTION_UNKNOWN: + break; + case FILTERDIRECTION_IMPORT: + if( mxImpl->mxInStream.is() ) + { + mxImpl->mxStorage = implCreateStorage( mxImpl->mxInStream ); + bRet = mxImpl->mxStorage && importDocument(); + } + break; + case FILTERDIRECTION_EXPORT: + if( mxImpl->mxOutStream.is() ) + { + mxImpl->mxStorage = implCreateStorage( mxImpl->mxOutStream ); + bRet = mxImpl->mxStorage && exportDocument() && implFinalizeExport( getMediaDescriptor() ); + } + break; + } + } + return bRet; +} + +void SAL_CALL FilterBase::cancel() +{ +} + +// protected + +Reference< XInputStream > FilterBase::implGetInputStream( MediaDescriptor& rMediaDesc ) const +{ + return rMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_INPUTSTREAM, Reference< XInputStream >() ); +} + +Reference< XStream > FilterBase::implGetOutputStream( MediaDescriptor& rMediaDesc ) const +{ + return rMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_STREAMFOROUTPUT, Reference< XStream >() ); +} + +bool FilterBase::implFinalizeExport( MediaDescriptor& /*rMediaDescriptor*/ ) +{ + return true; +} + +Reference< XStream > const & FilterBase::getMainDocumentStream( ) const +{ + return mxImpl->mxOutStream; +} + +// private + +void FilterBase::setMediaDescriptor( const Sequence< PropertyValue >& rMediaDescSeq ) +{ + mxImpl->maMediaDesc << rMediaDescSeq; + + switch( mxImpl->meDirection ) + { + case FILTERDIRECTION_UNKNOWN: + OSL_FAIL( "FilterBase::setMediaDescriptor - invalid filter direction" ); + break; + case FILTERDIRECTION_IMPORT: + mxImpl->maMediaDesc.addInputStream(); + mxImpl->mxInStream = implGetInputStream( mxImpl->maMediaDesc ); + OSL_ENSURE( mxImpl->mxInStream.is(), "FilterBase::setMediaDescriptor - missing input stream" ); + break; + case FILTERDIRECTION_EXPORT: + mxImpl->mxOutStream = implGetOutputStream( mxImpl->maMediaDesc ); + OSL_ENSURE( mxImpl->mxOutStream.is(), "FilterBase::setMediaDescriptor - missing output stream" ); + break; + } + + mxImpl->maFileUrl = mxImpl->maMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_URL, OUString() ); + mxImpl->mxTargetFrame = mxImpl->maMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_FRAME, Reference< XFrame >() ); + mxImpl->mxStatusIndicator = mxImpl->maMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_STATUSINDICATOR, Reference< XStatusIndicator >() ); + mxImpl->mxInteractionHandler = mxImpl->maMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_INTERACTIONHANDLER, Reference< XInteractionHandler >() ); + mxImpl->mxParentShape = mxImpl->maMediaDesc.getUnpackedValueOrDefault( "ParentShape", mxImpl->mxParentShape ); + mxImpl->maFilterData = mxImpl->maMediaDesc.getUnpackedValueOrDefault( "FilterData", Sequence< PropertyValue >() ); + + // Check for ISO OOXML + OUString sFilterName = mxImpl->maMediaDesc.getUnpackedValueOrDefault( "FilterName", OUString() ); + try + { + Reference<XMultiServiceFactory> xFactory(getComponentContext()->getServiceManager(), UNO_QUERY_THROW); + Reference<XNameAccess> xFilters(xFactory->createInstance("com.sun.star.document.FilterFactory" ), UNO_QUERY_THROW ); + Any aValues = xFilters->getByName( sFilterName ); + Sequence<PropertyValue > aPropSeq; + aValues >>= aPropSeq; + SequenceAsHashMap aProps( aPropSeq ); + + sal_Int32 nVersion = aProps.getUnpackedValueOrDefault( "FileFormatVersion", sal_Int32( 0 ) ); + mxImpl->meVersion = OoxmlVersion( nVersion ); + } + catch ( const Exception& ) + { + // Not ISO OOXML + } +} + +GraphicHelper* FilterBase::implCreateGraphicHelper() const +{ + // default: return base implementation without any special behaviour + return new GraphicHelper( mxImpl->mxComponentContext, mxImpl->mxTargetFrame, mxImpl->mxStorage ); +} + +bool FilterBase::exportVBA() const +{ + return mxImpl->mbExportVBA; +} + +bool FilterBase::isExportTemplate() const +{ + return mxImpl->mbExportTemplate; +} + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/core/filterdetect.cxx b/oox/source/core/filterdetect.cxx new file mode 100644 index 000000000..455d9a8b5 --- /dev/null +++ b/oox/source/core/filterdetect.cxx @@ -0,0 +1,479 @@ +/* -*- 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/filterdetect.hxx> + +#include <com/sun/star/io/XStream.hpp> +#include <comphelper/docpasswordhelper.hxx> +#include <unotools/mediadescriptor.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <oox/core/fastparser.hxx> +#include <oox/helper/attributelist.hxx> +#include <oox/helper/zipstorage.hxx> +#include <oox/ole/olestorage.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/tokens.hxx> + +#include <oox/crypto/DocumentDecryption.hxx> + +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/beans/NamedValue.hpp> + +using namespace ::com::sun::star; + +namespace oox::core { + +using namespace ::com::sun::star::beans; +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; +using namespace ::com::sun::star::uri; + +using utl::MediaDescriptor; +using comphelper::IDocPasswordVerifier; +using comphelper::DocPasswordVerifierResult; + +FilterDetectDocHandler::FilterDetectDocHandler( const Reference< XComponentContext >& rxContext, OUString& rFilterName, const OUString& rFileName ) : + mrFilterName( rFilterName ), + maFileName(rFileName), + maOOXMLVariant( OOXMLVariant::ECMA_Transitional ), + mxContext( rxContext ) +{ + maContextStack.reserve( 2 ); +} + +FilterDetectDocHandler::~FilterDetectDocHandler() +{ +} + +void SAL_CALL FilterDetectDocHandler::startDocument() +{ +} + +void SAL_CALL FilterDetectDocHandler::endDocument() +{ +} + +void SAL_CALL FilterDetectDocHandler::processingInstruction( const OUString& /*rTarget*/, const OUString& /*rData*/ ) +{ +} + +void SAL_CALL FilterDetectDocHandler::setDocumentLocator( const Reference<XLocator>& /*xLocator*/ ) +{ +} + +void SAL_CALL FilterDetectDocHandler::startFastElement( + sal_Int32 nElement, const Reference< XFastAttributeList >& rAttribs ) +{ + AttributeList aAttribs( rAttribs ); + switch ( nElement ) + { + // cases for _rels/.rels + case PR_TOKEN( Relationships ): + break; + case PR_TOKEN( Relationship ): + if( !maContextStack.empty() && (maContextStack.back() == PR_TOKEN( Relationships )) ) + parseRelationship( aAttribs ); + break; + + // cases for [Content_Types].xml + case PC_TOKEN( Types ): + break; + case PC_TOKEN( Default ): + if( !maContextStack.empty() && (maContextStack.back() == PC_TOKEN( Types )) ) + parseContentTypesDefault( aAttribs ); + break; + case PC_TOKEN( Override ): + if( !maContextStack.empty() && (maContextStack.back() == PC_TOKEN( Types )) ) + parseContentTypesOverride( aAttribs ); + break; + } + maContextStack.push_back( nElement ); +} + +void SAL_CALL FilterDetectDocHandler::startUnknownElement( + const OUString& /*Namespace*/, const OUString& /*Name*/, const Reference<XFastAttributeList>& /*Attribs*/ ) +{ +} + +void SAL_CALL FilterDetectDocHandler::endFastElement( sal_Int32 /*nElement*/ ) +{ + maContextStack.pop_back(); +} + +void SAL_CALL FilterDetectDocHandler::endUnknownElement( + const OUString& /*Namespace*/, const OUString& /*Name*/ ) +{ +} + +Reference<XFastContextHandler> SAL_CALL FilterDetectDocHandler::createFastChildContext( + sal_Int32 /*Element*/, const Reference<XFastAttributeList>& /*Attribs*/ ) +{ + return this; +} + +Reference<XFastContextHandler> SAL_CALL FilterDetectDocHandler::createUnknownChildContext( + const OUString& /*Namespace*/, const OUString& /*Name*/, const Reference<XFastAttributeList>& /*Attribs*/) +{ + return this; +} + +void SAL_CALL FilterDetectDocHandler::characters( const OUString& /*aChars*/ ) +{ +} + +void FilterDetectDocHandler::parseRelationship( const AttributeList& rAttribs ) +{ + OUString aType = rAttribs.getString( XML_Type, OUString() ); + + // tdf#131936 Remember filter when opening file as 'Office Open XML Text' + if (aType.startsWithIgnoreAsciiCase("http://schemas.openxmlformats.org/officedocument/2006/relationships/metadata/core-properties")) + maOOXMLVariant = OOXMLVariant::ISO_Transitional; + else if (aType.startsWithIgnoreAsciiCase("http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties")) + maOOXMLVariant = OOXMLVariant::ECMA_Transitional; + else if (aType.startsWithIgnoreAsciiCase("http://purl.oclc.org/ooxml/officeDocument")) + maOOXMLVariant = OOXMLVariant::ISO_Strict; + + if ( aType != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" // OOXML Transitional + && aType != "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument" ) //OOXML strict + return; + + Reference<XUriReferenceFactory> xFactory = UriReferenceFactory::create( mxContext ); + try + { + // use '/' to representent the root of the zip package ( and provide a 'file' scheme to + // keep the XUriReference implementation happy ) + Reference< XUriReference > xBase = xFactory->parse( "file:///" ); + + Reference< XUriReference > xPart = xFactory->parse( rAttribs.getString( XML_Target, OUString() ) ); + Reference< XUriReference > xAbs = xFactory->makeAbsolute( xBase, xPart, true, RelativeUriExcessParentSegments_RETAIN ); + + if ( xAbs.is() ) + maTargetPath = xAbs->getPath(); + } + catch( const Exception& ) + { + } +} + +OUString FilterDetectDocHandler::getFilterNameFromContentType( std::u16string_view rContentType, const OUString& rFileName ) +{ + bool bDocm = rFileName.endsWithIgnoreAsciiCase(".docm"); + + if( rContentType == u"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" && !bDocm ) + { + switch (maOOXMLVariant) + { + case OOXMLVariant::ISO_Transitional: + case OOXMLVariant::ISO_Strict: // Not supported, map to ISO transitional + return "writer_OOXML"; + case OOXMLVariant::ECMA_Transitional: + return "writer_MS_Word_2007"; + } + } + + if( rContentType == u"application/vnd.ms-word.document.macroEnabled.main+xml" || bDocm ) + return "writer_MS_Word_2007_VBA"; + + if( rContentType == u"application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml" || + rContentType == u"application/vnd.ms-word.template.macroEnabledTemplate.main+xml" ) + { + switch (maOOXMLVariant) + { + case OOXMLVariant::ISO_Transitional: + case OOXMLVariant::ISO_Strict: // Not supported, map to ISO transitional + return "writer_OOXML_Text_Template"; + case OOXMLVariant::ECMA_Transitional: + return "writer_MS_Word_2007_Template"; + } + } + + if( rContentType == u"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml") + return "MS Excel 2007 XML"; + + if (rContentType == u"application/vnd.ms-excel.sheet.macroEnabled.main+xml") + return "MS Excel 2007 VBA XML"; + + if( rContentType == u"application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml" || + rContentType == u"application/vnd.ms-excel.template.macroEnabled.main+xml" ) + return "MS Excel 2007 XML Template"; + + if ( rContentType == u"application/vnd.ms-excel.sheet.binary.macroEnabled.main" ) + return "MS Excel 2007 Binary"; + + if (rContentType == u"application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml") + return "MS PowerPoint 2007 XML"; + + if (rContentType == u"application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml") + return "MS PowerPoint 2007 XML VBA"; + + if( rContentType == u"application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml" || + rContentType == u"application/vnd.ms-powerpoint.slideshow.macroEnabled.main+xml" ) + return "MS PowerPoint 2007 XML AutoPlay"; + + if( rContentType == u"application/vnd.openxmlformats-officedocument.presentationml.template.main+xml" || + rContentType == u"application/vnd.ms-powerpoint.template.macroEnabled.main+xml" ) + return "MS PowerPoint 2007 XML Template"; + + return OUString(); +} + +void FilterDetectDocHandler::parseContentTypesDefault( const AttributeList& rAttribs ) +{ + // only if no overridden part name found + if( mrFilterName.isEmpty() ) + { + // check if target path ends with extension + OUString aExtension = rAttribs.getString( XML_Extension, OUString() ); + sal_Int32 nExtPos = maTargetPath.getLength() - aExtension.getLength(); + if( (nExtPos > 0) && (maTargetPath[ nExtPos - 1 ] == '.') && maTargetPath.match( aExtension, nExtPos ) ) + mrFilterName = getFilterNameFromContentType( rAttribs.getString( XML_ContentType, OUString() ), maFileName ); + } +} + +void FilterDetectDocHandler::parseContentTypesOverride( const AttributeList& rAttribs ) +{ + if( rAttribs.getString( XML_PartName, OUString() ) == maTargetPath ) + mrFilterName = getFilterNameFromContentType( rAttribs.getString( XML_ContentType, OUString() ), maFileName ); +} + +FilterDetect::FilterDetect( const Reference< XComponentContext >& rxContext ) : + mxContext( rxContext, UNO_SET_THROW ) +{ +} + +FilterDetect::~FilterDetect() +{ +} + +namespace +{ + +bool lclIsZipPackage( const Reference< XComponentContext >& rxContext, const Reference< XInputStream >& rxInStrm ) +{ + ZipStorage aZipStorage( rxContext, rxInStrm ); + return aZipStorage.isStorage(); +} + +class PasswordVerifier : public IDocPasswordVerifier +{ +public: + explicit PasswordVerifier( crypto::DocumentDecryption& aDecryptor ); + + virtual DocPasswordVerifierResult verifyPassword( const OUString& rPassword, Sequence<NamedValue>& rEncryptionData ) override; + + virtual DocPasswordVerifierResult verifyEncryptionData( const Sequence<NamedValue>& rEncryptionData ) override; +private: + crypto::DocumentDecryption& mDecryptor; +}; + +PasswordVerifier::PasswordVerifier( crypto::DocumentDecryption& aDecryptor ) : + mDecryptor(aDecryptor) +{} + +comphelper::DocPasswordVerifierResult PasswordVerifier::verifyPassword( const OUString& rPassword, Sequence<NamedValue>& rEncryptionData ) +{ + try + { + if (mDecryptor.generateEncryptionKey(rPassword)) + rEncryptionData = mDecryptor.createEncryptionData(rPassword); + } + catch (...) + { + // Any exception is a reason to abort + return comphelper::DocPasswordVerifierResult::Abort; + } + + return rEncryptionData.hasElements() ? comphelper::DocPasswordVerifierResult::OK : comphelper::DocPasswordVerifierResult::WrongPassword; +} + +comphelper::DocPasswordVerifierResult PasswordVerifier::verifyEncryptionData( const Sequence<NamedValue>& ) +{ + return comphelper::DocPasswordVerifierResult::WrongPassword; +} + +} // namespace + +Reference< XInputStream > FilterDetect::extractUnencryptedPackage( MediaDescriptor& rMediaDescriptor ) const +{ + // try the plain input stream + Reference<XInputStream> xInputStream( rMediaDescriptor[ MediaDescriptor::PROP_INPUTSTREAM ], UNO_QUERY ); + if( !xInputStream.is() || lclIsZipPackage( mxContext, xInputStream ) ) + return xInputStream; + + // check if a temporary file is passed in the 'ComponentData' property + Reference<XStream> xDecrypted( rMediaDescriptor.getComponentDataEntry( "DecryptedPackage" ), UNO_QUERY ); + if( xDecrypted.is() ) + { + Reference<XInputStream> xDecryptedInputStream = xDecrypted->getInputStream(); + if( lclIsZipPackage( mxContext, xDecryptedInputStream ) ) + return xDecryptedInputStream; + } + + // try to decrypt an encrypted OLE package + oox::ole::OleStorage aOleStorage( mxContext, xInputStream, false ); + if( aOleStorage.isStorage() ) + { + try + { + crypto::DocumentDecryption aDecryptor(mxContext, aOleStorage); + + if( aDecryptor.readEncryptionInfo() ) + { + /* "VelvetSweatshop" is the built-in default encryption + password used by MS Excel for the "workbook protection" + feature with password. Try this first before prompting the + user for a password. */ + std::vector<OUString> aDefaultPasswords; + aDefaultPasswords.emplace_back("VelvetSweatshop"); + + /* Use the comphelper password helper to request a password. + This helper returns either with the correct password + (according to the verifier), or with an empty string if + user has cancelled the password input dialog. */ + PasswordVerifier aVerifier( aDecryptor ); + Sequence<NamedValue> aEncryptionData = rMediaDescriptor.requestAndVerifyDocPassword( + aVerifier, + comphelper::DocPasswordRequestType::MS, + &aDefaultPasswords ); + + if( !aEncryptionData.hasElements() ) + { + rMediaDescriptor[ MediaDescriptor::PROP_ABORTED ] <<= true; + } + else + { + // create MemoryStream for unencrypted package - rather not put this in a tempfile + Reference<XStream> const xTempStream( + mxContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.comp.MemoryStream", mxContext), + UNO_QUERY_THROW); + + // if decryption was unsuccessful (corrupted file or any other reason) + if (!aDecryptor.decrypt(xTempStream)) + { + rMediaDescriptor[ MediaDescriptor::PROP_ABORTED ] <<= true; + } + else + { + // store temp file in media descriptor to keep it alive + rMediaDescriptor.setComponentDataEntry( "DecryptedPackage", Any( xTempStream ) ); + + Reference<XInputStream> xDecryptedInputStream = xTempStream->getInputStream(); + if( lclIsZipPackage( mxContext, xDecryptedInputStream ) ) + return xDecryptedInputStream; + } + } + } + } + catch( const Exception& ) + { + } + } + return Reference<XInputStream>(); +} + +// com.sun.star.lang.XServiceInfo interface ----------------------------------- + +OUString SAL_CALL FilterDetect::getImplementationName() +{ + return "com.sun.star.comp.oox.FormatDetector"; +} + +sal_Bool SAL_CALL FilterDetect::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SAL_CALL FilterDetect::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ExtendedTypeDetection" }; +} + +// com.sun.star.document.XExtendedFilterDetection interface ------------------- + +OUString SAL_CALL FilterDetect::detect( Sequence< PropertyValue >& rMediaDescSeq ) +{ + OUString aFilterName; + MediaDescriptor aMediaDescriptor( rMediaDescSeq ); + + try + { + aMediaDescriptor.addInputStream(); + + /* Get the unencrypted input stream. This may include creation of a + temporary file that contains the decrypted package. This temporary + file will be stored in the 'ComponentData' property of the media + descriptor. */ + Reference< XInputStream > xInputStream( extractUnencryptedPackage( aMediaDescriptor ), UNO_SET_THROW ); + + // stream must be a ZIP package + ZipStorage aZipStorage( mxContext, xInputStream ); + if( aZipStorage.isStorage() ) + { + // create the fast parser, register the XML namespaces, set document handler + FastParser aParser; + aParser.registerNamespace( NMSP_packageRel ); + aParser.registerNamespace( NMSP_officeRel ); + aParser.registerNamespace( NMSP_packageContentTypes ); + + OUString aFileName; + aMediaDescriptor[utl::MediaDescriptor::PROP_URL] >>= aFileName; + + aParser.setDocumentHandler( new FilterDetectDocHandler( mxContext, aFilterName, aFileName ) ); + + /* Parse '_rels/.rels' to get the target path and '[Content_Types].xml' + to determine the content type of the part at the target path. */ + aParser.parseStream( aZipStorage, "_rels/.rels" ); + aParser.parseStream( aZipStorage, "[Content_Types].xml" ); + } + } + catch( const Exception& ) + { + if ( aMediaDescriptor.getUnpackedValueOrDefault( MediaDescriptor::PROP_ABORTED, false ) ) + /* The user chose to abort detection, e.g. by hitting 'Cancel' in the password input dialog, + so we have to return non-empty type name to abort the detection loop. The loading code is + supposed to check whether the "Aborted" flag is present in the descriptor, and to not attempt + to actually load the file then. + + The returned type name is the one we got as an input, which typically was detected by the flat + detection (i.e. by file extension), so normally that's the correct one. Also at this point we + already know that the file is OLE encrypted package, so trying with other type detectors doesn't + make much sense anyway. + */ + aFilterName = aMediaDescriptor.getUnpackedValueOrDefault( MediaDescriptor::PROP_TYPENAME, OUString() ); + } + + // write back changed media descriptor members + aMediaDescriptor >> rMediaDescSeq; + return aFilterName; +} + +} // namespace oox::core + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +com_sun_star_comp_oox_FormatDetector_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new oox::core::FilterDetect(pCtx)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/core/fragmenthandler.cxx b/oox/source/core/fragmenthandler.cxx new file mode 100644 index 000000000..22319fda6 --- /dev/null +++ b/oox/source/core/fragmenthandler.cxx @@ -0,0 +1,119 @@ +/* -*- 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/fragmenthandler.hxx> + +#include <oox/core/xmlfilterbase.hxx> + +namespace oox::core { + +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; + +FragmentBaseData::FragmentBaseData( XmlFilterBase& rFilter, const OUString& rFragmentPath, RelationsRef const & xRelations ) : + mrFilter( rFilter ), + maFragmentPath( rFragmentPath ), + mxRelations( xRelations ) +{ +} + +FragmentHandler::FragmentHandler( XmlFilterBase& rFilter, const OUString& rFragmentPath ) : + FragmentHandler_BASE( std::make_shared<FragmentBaseData>( rFilter, rFragmentPath, rFilter.importRelations( rFragmentPath ) ) ) +{ +} + +FragmentHandler::FragmentHandler( XmlFilterBase& rFilter, const OUString& rFragmentPath, RelationsRef xRelations ) : + FragmentHandler_BASE( std::make_shared<FragmentBaseData>( rFilter, rFragmentPath, xRelations ) ) +{ +} + +FragmentHandler::~FragmentHandler() +{ +} + +// com.sun.star.xml.sax.XFastDocumentHandler interface ------------------------ + +void FragmentHandler::startDocument() +{ +} + +void FragmentHandler::endDocument() +{ +} + +void FragmentHandler::processingInstruction( const OUString& /*rTarget*/, const OUString& /*rData*/ ) +{ +} + +void FragmentHandler::setDocumentLocator( const Reference< XLocator >& rxLocator ) +{ + implSetLocator( rxLocator ); +} + +// com.sun.star.xml.sax.XFastContextHandler interface ------------------------- + +void FragmentHandler::startFastElement( sal_Int32, const Reference< XFastAttributeList >& ) +{ +} + +void FragmentHandler::startUnknownElement( const OUString&, const OUString&, const Reference< XFastAttributeList >& ) +{ +} + +void FragmentHandler::endFastElement( sal_Int32 ) +{ +} + +void FragmentHandler::endUnknownElement( const OUString&, const OUString& ) +{ +} + +Reference< XFastContextHandler > FragmentHandler::createFastChildContext( sal_Int32, const Reference< XFastAttributeList >& ) +{ + return nullptr; +} + +Reference< XFastContextHandler > FragmentHandler::createUnknownChildContext( const OUString&, const OUString&, const Reference< XFastAttributeList >& ) +{ + return nullptr; +} + +void FragmentHandler::characters( const OUString& ) +{ +} + +// XML stream handling -------------------------------------------------------- + +Reference< XInputStream > FragmentHandler::openFragmentStream() const +{ + return getFilter().openInputStream( getFragmentPath() ); +} + +// binary records ------------------------------------------------------------- + +const RecordInfo* FragmentHandler::getRecordInfos() const +{ + // default: no support for binary records + return nullptr; +} + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/core/fragmenthandler2.cxx b/oox/source/core/fragmenthandler2.cxx new file mode 100644 index 000000000..e4eeef076 --- /dev/null +++ b/oox/source/core/fragmenthandler2.cxx @@ -0,0 +1,155 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/frame/XModel.hpp> +#include <oox/core/fragmenthandler2.hxx> +#include <oox/core/xmlfilterbase.hxx> +#include <oox/helper/attributelist.hxx> +#include <oox/token/namespaces.hxx> + +namespace oox::core { + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; + +FragmentHandler2::FragmentHandler2( XmlFilterBase& rFilter, const OUString& rFragmentPath, bool bEnableTrimSpace ) : + FragmentHandler( rFilter, rFragmentPath ), + ContextHandler2Helper( bEnableTrimSpace, rFilter ) +{ +} + +FragmentHandler2::~FragmentHandler2() +{ +} + +// com.sun.star.xml.sax.XFastDocumentHandler interface -------------------- + +void SAL_CALL FragmentHandler2::startDocument() +{ + initializeImport(); +} + +void SAL_CALL FragmentHandler2::endDocument() +{ + finalizeImport(); +} + +// com.sun.star.xml.sax.XFastContextHandler interface ------------------------- + +Reference< XFastContextHandler > SAL_CALL FragmentHandler2::createFastChildContext( + sal_Int32 nElement, const Reference< XFastAttributeList >& rxAttribs ) +{ + if( getNamespace( nElement ) == NMSP_mce ) // TODO for checking 'Ignorable' + { + if( prepareMceContext( nElement, AttributeList( rxAttribs ) ) ) + return getFastContextHandler(); + return nullptr; + } + return implCreateChildContext( nElement, rxAttribs ); +} + +void SAL_CALL FragmentHandler2::startFastElement( + sal_Int32 nElement, const Reference< XFastAttributeList >& rxAttribs ) +{ + implStartElement( nElement, rxAttribs ); +} + +void SAL_CALL FragmentHandler2::characters( const OUString& rChars ) +{ + implCharacters( rChars ); +} + +void SAL_CALL FragmentHandler2::endFastElement( sal_Int32 nElement ) +{ + /* If MCE */ + switch( nElement ) + { + case MCE_TOKEN( AlternateContent ): + removeMCEState(); + break; + } + + implEndElement( nElement ); +} + +// oox.core.ContextHandler interface ------------------------------------------ + +ContextHandlerRef FragmentHandler2::createRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) +{ + return implCreateRecordContext( nRecId, rStrm ); +} + +void FragmentHandler2::startRecord( sal_Int32 nRecId, SequenceInputStream& rStrm ) +{ + implStartRecord( nRecId, rStrm ); +} + +void FragmentHandler2::endRecord( sal_Int32 nRecId ) +{ + implEndRecord( nRecId ); +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef FragmentHandler2::onCreateContext( sal_Int32, const AttributeList& ) +{ + return nullptr; +} + +void FragmentHandler2::onStartElement( const AttributeList& ) +{ +} + +void FragmentHandler2::onCharacters( const OUString& ) +{ +} + +void FragmentHandler2::onEndElement() +{ +} + +ContextHandlerRef FragmentHandler2::onCreateRecordContext( sal_Int32, SequenceInputStream& ) +{ + return nullptr; +} + +void FragmentHandler2::onStartRecord( SequenceInputStream& ) +{ +} + +void FragmentHandler2::onEndRecord() +{ +} + +// oox.core.FragmentHandler2 interface ---------------------------------------- + +void FragmentHandler2::initializeImport() +{ +} + +void FragmentHandler2::finalizeImport() +{ +} + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 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: */ diff --git a/oox/source/core/relations.cxx b/oox/source/core/relations.cxx new file mode 100644 index 000000000..f927c542c --- /dev/null +++ b/oox/source/core/relations.cxx @@ -0,0 +1,158 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> +#include <string_view> + +#include <oox/core/relations.hxx> + +namespace oox::core { + +namespace { + +std::u16string_view lclRemoveFileName( std::u16string_view rPath ) +{ + size_t idx = rPath.rfind( '/' ); + if (idx == std::u16string_view::npos) + return std::u16string_view(); + return rPath.substr( 0, idx ); +} + +OUString lclAppendFileName( std::u16string_view rPath, const OUString& rFileName ) +{ + return rPath.empty() ? rFileName : + rPath + OUStringChar('/') + rFileName; +} + +OUString createOfficeDocRelationTypeTransitional(std::u16string_view rType) +{ + return OUString::Concat("http://schemas.openxmlformats.org/officeDocument/2006/relationships/") + + rType; +} + +OUString createOfficeDocRelationTypeStrict(std::u16string_view rType) +{ + return OUString::Concat("http://purl.oclc.org/ooxml/officeDocument/relationships/") + rType; +} + +} + +Relations::Relations( const OUString& rFragmentPath ) + : maFragmentPath( rFragmentPath ) +{ +} + +const Relation* Relations::getRelationFromRelId( const OUString& rId ) const +{ + ::std::map< OUString, Relation >::const_iterator aIt = maMap.find( rId ); + return (aIt == maMap.end()) ? nullptr : &aIt->second; +} + +const Relation* Relations::getRelationFromFirstType( std::u16string_view rType ) const +{ + for (auto const& elem : maMap) + if( elem.second.maType.equalsIgnoreAsciiCase( rType ) ) + return &elem.second; + return nullptr; +} + +RelationsRef Relations::getRelationsFromTypeFromOfficeDoc( std::u16string_view rType ) const +{ + RelationsRef xRelations = std::make_shared<Relations>( maFragmentPath ); + for (auto const& elem : maMap) + if( elem.second.maType.equalsIgnoreAsciiCase( createOfficeDocRelationTypeTransitional(rType) ) || + elem.second.maType.equalsIgnoreAsciiCase( createOfficeDocRelationTypeStrict(rType) )) + xRelations->maMap[ elem.first ] = elem.second; + return xRelations; +} + +OUString Relations::getExternalTargetFromRelId( const OUString& rRelId ) const +{ + const Relation* pRelation = getRelationFromRelId( rRelId ); + return (pRelation && pRelation->mbExternal) ? pRelation->maTarget : OUString(); +} + +OUString Relations::getInternalTargetFromRelId( const OUString& rRelId ) const +{ + const Relation* pRelation = getRelationFromRelId( rRelId ); + return (pRelation && !pRelation->mbExternal) ? pRelation->maTarget : OUString(); +} + +OUString Relations::getFragmentPathFromRelation( const Relation& rRelation ) const +{ + // no target, no fragment path + if( rRelation.mbExternal || rRelation.maTarget.isEmpty() ) + return OUString(); + + // absolute target: return it without leading slash (#i100978) + if( rRelation.maTarget[ 0 ] == '/' ) + return rRelation.maTarget.copy( 1 ); + + // empty fragment path: return target + if( maFragmentPath.isEmpty() ) + return rRelation.maTarget; + + // resolve relative target path according to base path + OUString aPath( lclRemoveFileName( maFragmentPath ) ); + sal_Int32 nStartPos = 0; + while( nStartPos < rRelation.maTarget.getLength() ) + { + sal_Int32 nSepPos = rRelation.maTarget.indexOf( '/', nStartPos ); + if( nSepPos < 0 ) nSepPos = rRelation.maTarget.getLength(); + // append next directory name from aTarget to aPath, or remove last directory on '../' + if( (nStartPos + 2 == nSepPos) && (rRelation.maTarget[ nStartPos ] == '.') && (rRelation.maTarget[ nStartPos + 1 ] == '.') ) + aPath = lclRemoveFileName( aPath ); + else + aPath = lclAppendFileName( aPath, rRelation.maTarget.copy( nStartPos, nSepPos - nStartPos ) ); + // move nStartPos to next directory name + nStartPos = nSepPos + 1; + } + + return aPath; +} + +OUString Relations::getFragmentPathFromRelId( const OUString& rRelId ) const +{ + const Relation* pRelation = getRelationFromRelId( rRelId ); + return pRelation ? getFragmentPathFromRelation( *pRelation ) : OUString(); +} + +OUString Relations::getFragmentPathFromFirstType( std::u16string_view rType ) const +{ + const Relation* pRelation = getRelationFromFirstType( rType ); + return pRelation ? getFragmentPathFromRelation( *pRelation ) : OUString(); +} + +OUString Relations::getFragmentPathFromFirstTypeFromOfficeDoc( std::u16string_view rType ) const +{ + OUString aTransitionalType(createOfficeDocRelationTypeTransitional(rType)); + const Relation* pRelation = getRelationFromFirstType( aTransitionalType ); + if(!pRelation) + { + OUString aStrictType = createOfficeDocRelationTypeStrict(rType); + pRelation = getRelationFromFirstType( aStrictType ); + } + return pRelation ? getFragmentPathFromRelation( *pRelation ) : OUString(); +} + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/core/relationshandler.cxx b/oox/source/core/relationshandler.cxx new file mode 100644 index 000000000..90f3ebcef --- /dev/null +++ b/oox/source/core/relationshandler.cxx @@ -0,0 +1,95 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include <oox/core/relationshandler.hxx> + +#include <sal/log.hxx> +#include <oox/helper/attributelist.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/tokens.hxx> + +namespace oox::core { + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; + +namespace { + +/* Build path to relations file from passed fragment path, e.g.: + 'path/path/file.xml' -> 'path/path/_rels/file.xml.rels' + 'file.xml' -> '_rels/file.xml.rels' + '' -> '_rels/.rels' + */ +OUString lclGetRelationsPath( const OUString& rFragmentPath ) +{ + sal_Int32 nPathLen = ::std::max< sal_Int32 >( rFragmentPath.lastIndexOf( '/' ) + 1, 0 ); + return OUString::Concat(std::u16string_view(rFragmentPath.getStr(), nPathLen )) + // file path including slash + "_rels/" + // additional '_rels/' path + std::u16string_view(rFragmentPath.getStr() + nPathLen) + // file name after path + ".rels"; // '.rels' suffix +} + +} // namespace + +RelationsFragment::RelationsFragment( XmlFilterBase& rFilter, const RelationsRef& xRelations ) : + FragmentHandler( rFilter, lclGetRelationsPath( xRelations->getFragmentPath() ), xRelations ), + mxRelations( xRelations ) +{ +} + +Reference< XFastContextHandler > RelationsFragment::createFastChildContext( + sal_Int32 nElement, const Reference< XFastAttributeList >& rxAttribs ) +{ + Reference< XFastContextHandler > xRet; + AttributeList aAttribs( rxAttribs ); + switch( nElement ) + { + case PR_TOKEN( Relationship ): + { + Relation aRelation; + aRelation.maId = aAttribs.getString( XML_Id, OUString() ); + aRelation.maType = aAttribs.getString( XML_Type, OUString() ); + aRelation.maTarget = aAttribs.getString( XML_Target, OUString() ); + if( !aRelation.maId.isEmpty() && !aRelation.maType.isEmpty() && !aRelation.maTarget.isEmpty() ) + { + sal_Int32 nTargetMode = aAttribs.getToken( XML_TargetMode, XML_Internal ); + SAL_WARN_IF( (nTargetMode != XML_Internal) && (nTargetMode != XML_External), "oox", + "RelationsFragment::createFastChildContext - unexpected target mode, assuming external" ); + aRelation.mbExternal = nTargetMode != XML_Internal; + + SAL_WARN_IF( mxRelations->count( aRelation.maId ) != 0, "oox", + "RelationsFragment::createFastChildContext - relation identifier exists already" ); + mxRelations->emplace( aRelation.maId, aRelation ); + } + } + break; + case PR_TOKEN( Relationships ): + xRet = getFastContextHandler(); + break; + } + return xRet; +} + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/core/xmlfilterbase.cxx b/oox/source/core/xmlfilterbase.cxx new file mode 100644 index 000000000..e8e1a1389 --- /dev/null +++ b/oox/source/core/xmlfilterbase.cxx @@ -0,0 +1,1235 @@ +/* -*- 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/xmlfilterbase.hxx> + +#include <cstdio> +#include <string_view> + +#include <com/sun/star/beans/XPropertyAccess.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/Pair.hpp> +#include <com/sun/star/embed/XRelationshipAccess.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/xml/sax/XFastSAXSerializable.hpp> +#include <com/sun/star/xml/sax/XSAXSerializable.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <o3tl/any.hxx> +#include <unotools/mediadescriptor.hxx> +#include <unotools/docinfohelper.hxx> +#include <sax/fshelper.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <oox/core/fastparser.hxx> +#include <oox/core/fragmenthandler.hxx> +#include <oox/core/recordparser.hxx> +#include <oox/core/relationshandler.hxx> +#include <oox/helper/propertyset.hxx> +#include <oox/helper/zipstorage.hxx> +#include <oox/ole/olestorage.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/relationship.hxx> +#include <oox/token/properties.hxx> +#include <oox/token/tokens.hxx> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XOOXMLDocumentPropertiesImporter.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <comphelper/processfactory.hxx> +#include <oox/core/filterdetect.hxx> +#include <comphelper/stl_types.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/ofopxmlhelper.hxx> + +#include <oox/crypto/DocumentEncryption.hxx> +#include <tools/urlobj.hxx> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/Duration.hpp> +#include <sax/tools/converter.hxx> +#include <oox/token/namespacemap.hxx> +#include <editeng/unoprnms.hxx> +#include <o3tl/sorted_vector.hxx> + +using ::com::sun::star::xml::dom::DocumentBuilder; +using ::com::sun::star::xml::dom::XDocument; +using ::com::sun::star::xml::dom::XDocumentBuilder; + +namespace oox::core { + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::embed; +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; + +using utl::MediaDescriptor; +using ::sax_fastparser::FSHelperPtr; +using ::sax_fastparser::FastSerializerHelper; + +namespace { + +const Sequence< beans::Pair< OUString, sal_Int32 > >& NamespaceIds() +{ + static const Sequence< beans::Pair< OUString, sal_Int32 > > SINGLETON + { + {"http://www.w3.org/XML/1998/namespace", NMSP_xml}, + {"http://schemas.openxmlformats.org/package/2006/relationships", + NMSP_packageRel}, + {"http://schemas.openxmlformats.org/officeDocument/2006/relationships", + NMSP_officeRel}, + {"http://purl.oclc.org/ooxml/officeDocument/relationships", + NMSP_officeRel}, + {"http://schemas.openxmlformats.org/drawingml/2006/main", NMSP_dml}, + {"http://purl.oclc.org/ooxml/drawingml/main", NMSP_dml}, + {"http://schemas.openxmlformats.org/drawingml/2006/diagram", + NMSP_dmlDiagram}, + {"http://purl.oclc.org/ooxml/drawingml/diagram", NMSP_dmlDiagram}, + {"http://schemas.openxmlformats.org/drawingml/2006/chart", + NMSP_dmlChart}, + {"http://schemas.openxmlformats.org/drawingml/2006/chartDrawing", + NMSP_dmlChartDr}, + {"urn:schemas-microsoft-com:vml", NMSP_vml}, + {"urn:schemas-microsoft-com:office:office", NMSP_vmlOffice}, + {"urn:schemas-microsoft-com:office:word", NMSP_vmlWord}, + {"urn:schemas-microsoft-com:office:excel", NMSP_vmlExcel}, + {"urn:schemas-microsoft-com:office:powerpoint", NMSP_vmlPowerpoint}, + {"http://schemas.microsoft.com/office/2006/activeX", NMSP_ax}, + {"http://schemas.openxmlformats.org/spreadsheetml/2006/main", + NMSP_xls}, + {"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", + NMSP_xm}, + {"http://schemas.microsoft.com/office/excel/2006/main", + NMSP_dmlSpreadDr}, + {"http://schemas.openxmlformats.org/presentationml/2006/main", + NMSP_ppt}, + {"http://schemas.openxmlformats.org/markup-compatibility/2006", + NMSP_mce}, + {"http://schemas.openxmlformats.org/spreadsheetml/2006/main/v2", + NMSP_mceTest}, + {"http://schemas.openxmlformats.org/officeDocument/2006/math", + NMSP_officeMath}, + {"http://schemas.microsoft.com/office/drawing/2008/diagram", + NMSP_dsp}, + {"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main", + NMSP_xls14Lst}, + {"http://schemas.libreoffice.org/", NMSP_loext}, + {"http://schemas.microsoft.com/office/drawing/2010/main", + NMSP_a14}, + {"http://schemas.microsoft.com/office/powerpoint/2010/main", + NMSP_p14}, + {"http://schemas.microsoft.com/office/powerpoint/2012/main", + NMSP_p15}, + {"http://schemas.microsoft.com/office/spreadsheetml/2011/1/ac", + NMSP_x12ac}, + {"http://schemas.microsoft.com/office/drawing/2012/chart", + NMSP_c15}, + {"http://schemas.microsoft.com/office/spreadsheetml/2015/revision2", + NMSP_xr2}, + }; + return SINGLETON; +}; + +void registerNamespaces( FastParser& rParser ) +{ + const Sequence< beans::Pair<OUString, sal_Int32> >& ids = NamespaceIds(); + + // Filter out duplicates: a namespace can have multiple URLs, think of + // strict vs transitional. + o3tl::sorted_vector<sal_Int32> aSet; + aSet.reserve(ids.getLength()); + for (const auto& rId : ids) + aSet.insert(rId.Second); + + for (auto const& elem : aSet) + rParser.registerNamespace(elem); +} + +} // namespace + +struct XmlFilterBaseImpl +{ + typedef RefMap< OUString, Relations > RelationsMap; + + FastParser maFastParser; + RelationsMap maRelationsMap; + TextFieldStack maTextFieldStack; + const NamespaceMap& mrNamespaceMap; + NamedShapePairs* mpDiagramFontHeights = nullptr; + + /// @throws RuntimeException + explicit XmlFilterBaseImpl(); +}; + +constexpr OUStringLiteral gaBinSuffix( u".bin" ); + +XmlFilterBaseImpl::XmlFilterBaseImpl() : + mrNamespaceMap(StaticNamespaceMap()) +{ + // register XML namespaces + registerNamespaces(maFastParser); +} + +XmlFilterBase::XmlFilterBase( const Reference< XComponentContext >& rxContext ) : + FilterBase( rxContext ), + mxImpl( new XmlFilterBaseImpl ), + mnRelId( 1 ), + mnMaxDocId( 0 ), + mbMSO2007(false), + mbMSO(false), + mbMissingExtDrawing(false) +{ +} + +XmlFilterBase::~XmlFilterBase() +{ + // #i118640# Reset the DocumentHandler at the FastSaxParser manually; this is + // needed since the mechanism is that instances of FragmentHandler execute + // their stuff (creating objects, setting attributes, ...) on being destroyed. + // They get destroyed by setting a new DocumentHandler. This also happens in + // the following implicit destruction chain of ~XmlFilterBaseImpl, but in that + // case it's member RelationsMap maRelationsMap will be destroyed, but maybe + // still be used by ~FragmentHandler -> crash. + mxImpl->maFastParser.clearDocumentHandler(); +} + +std::shared_ptr<::oox::drawingml::Theme> XmlFilterBase::getCurrentThemePtr() const +{ + // default returns empty ptr + return std::shared_ptr<::oox::drawingml::Theme>(); +} + +void XmlFilterBase::checkDocumentProperties(const Reference<XDocumentProperties>& xDocProps) +{ + mbMSO2007 = mbMSO = false; + if (!xDocProps->getGenerator().startsWithIgnoreAsciiCase("Microsoft")) + return; + mbMSO = true; + + uno::Reference<beans::XPropertyAccess> xUserDefProps(xDocProps->getUserDefinedProperties(), uno::UNO_QUERY); + if (!xUserDefProps.is()) + return; + + comphelper::SequenceAsHashMap aUserDefinedProperties(xUserDefProps->getPropertyValues()); + comphelper::SequenceAsHashMap::iterator it = aUserDefinedProperties.find("AppVersion"); + if (it == aUserDefinedProperties.end()) + return; + + OUString aValue; + if (!(it->second >>= aValue)) + return; + + if (!aValue.startsWithIgnoreAsciiCase("12.")) + return; + + SAL_INFO("oox", "a MSO 2007 document"); + mbMSO2007 = true; +} + +void XmlFilterBase::putPropertiesToDocumentGrabBag(const css::uno::Reference<css::lang::XComponent>& xDstDoc, + const comphelper::SequenceAsHashMap& rProperties) +{ + try + { + uno::Reference<beans::XPropertySet> xDocProps(xDstDoc, uno::UNO_QUERY); + if (xDocProps.is()) + { + uno::Reference<beans::XPropertySetInfo> xPropsInfo = xDocProps->getPropertySetInfo(); + + static constexpr OUStringLiteral aGrabBagPropName = u"InteropGrabBag"; + if (xPropsInfo.is() && xPropsInfo->hasPropertyByName(aGrabBagPropName)) + { + // get existing grab bag + comphelper::SequenceAsHashMap aGrabBag(xDocProps->getPropertyValue(aGrabBagPropName)); + + // put the new items + aGrabBag.update(rProperties); + + // put it back to the document + xDocProps->setPropertyValue(aGrabBagPropName, uno::Any(aGrabBag.getAsConstPropertyValueList())); + } + } + } + catch (const uno::Exception&) + { + SAL_WARN("oox","Failed to save documents grab bag"); + } +} + +void XmlFilterBase::importDocumentProperties() +{ + MediaDescriptor aMediaDesc( getMediaDescriptor() ); + Reference< XInputStream > xInputStream; + Reference< XComponentContext > xContext = getComponentContext(); + rtl::Reference< ::oox::core::FilterDetect > xDetector( new ::oox::core::FilterDetect( xContext ) ); + xInputStream = xDetector->extractUnencryptedPackage( aMediaDesc ); + Reference< XComponent > xModel = getModel(); + Reference< XStorage > xDocumentStorage ( + ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream( OFOPXML_STORAGE_FORMAT_STRING, xInputStream ) ); + Reference< XInterface > xTemp = xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.document.OOXMLDocumentPropertiesImporter", + xContext); + Reference< XOOXMLDocumentPropertiesImporter > xImporter( xTemp, UNO_QUERY ); + Reference< XDocumentPropertiesSupplier > xPropSupplier( xModel, UNO_QUERY); + Reference< XDocumentProperties > xDocProps = xPropSupplier->getDocumentProperties(); + xImporter->importProperties( xDocumentStorage, xDocProps ); + checkDocumentProperties(xDocProps); + + importCustomFragments(xDocumentStorage); +} + +FastParser* XmlFilterBase::createParser() +{ + FastParser* pParser = new FastParser; + registerNamespaces(*pParser); + return pParser; +} + +namespace { + +OUString getTransitionalRelationshipOfficeDocType(std::u16string_view rPart) +{ + return OUString::Concat("http://schemas.openxmlformats.org/officeDocument/2006/relationships/") + + rPart; +} + +OUString getStrictRelationshipOfficeDocType(std::u16string_view rPart) +{ + return OUString::Concat("http://purl.oclc.org/ooxml/officeDocument/relationships/") + rPart; +} + +} + +OUString XmlFilterBase::getFragmentPathFromFirstTypeFromOfficeDoc( std::u16string_view rPart ) +{ + // importRelations() caches the relations map for subsequence calls + const OUString aTransitionalRelationshipType = getTransitionalRelationshipOfficeDocType(rPart); + OUString aFragment = importRelations( OUString() )->getFragmentPathFromFirstType( aTransitionalRelationshipType ); + if(aFragment.isEmpty()) + { + const OUString aStrictRelationshipType = getStrictRelationshipOfficeDocType(rPart); + aFragment = importRelations( OUString() )->getFragmentPathFromFirstType( aStrictRelationshipType ); + } + + return aFragment; +} + +bool XmlFilterBase::importFragment( const rtl::Reference<FragmentHandler>& rxHandler ) +{ + FastParser aParser; + registerNamespaces(aParser); + return importFragment(rxHandler, aParser); +} + +bool XmlFilterBase::importFragment( const rtl::Reference<FragmentHandler>& rxHandler, FastParser& rParser ) +{ + OSL_ENSURE( rxHandler.is(), "XmlFilterBase::importFragment - missing fragment handler" ); + if( !rxHandler.is() ) + return false; + + // fragment handler must contain path to fragment stream + OUString aFragmentPath = rxHandler->getFragmentPath(); + OSL_ENSURE( !aFragmentPath.isEmpty(), "XmlFilterBase::importFragment - missing fragment path" ); + if( aFragmentPath.isEmpty() ) + return false; + + // try to import binary streams (fragment extension must be '.bin') + if (aFragmentPath.endsWith(gaBinSuffix)) + { + try + { + // try to open the fragment stream (this may fail - do not assert) + Reference< XInputStream > xInStrm( openInputStream( aFragmentPath ), UNO_SET_THROW ); + + // create the record parser + RecordParser aParser; + aParser.setFragmentHandler( rxHandler ); + + // create the input source and parse the stream + RecordInputSource aSource; + aSource.mxInStream = std::make_shared<BinaryXInputStream>( xInStrm, true ); + aSource.maSystemId = aFragmentPath; + aParser.parseStream( aSource ); + return true; + } + catch( Exception& ) + { + } + return false; + } + + // get the XFastDocumentHandler interface from the fragment handler + if( !rxHandler.is() ) + return false; + + // try to import XML stream + try + { + /* Try to open the fragment stream (may fail, do not throw/assert). + Using the virtual function openFragmentStream() allows a document + handler to create specialized input streams, e.g. VML streams that + have to preprocess the raw input data. */ + Reference< XInputStream > xInStrm = rxHandler->openFragmentStream(); + /* tdf#100084 Check again the aFragmentPath route with lowercase file name + TODO: complete handling of case-insensitive file paths */ + if ( !xInStrm.is() ) + { + sal_Int32 nPathLen = aFragmentPath.lastIndexOf('/') + 1; + OUString fileName = aFragmentPath.copy(nPathLen); + OUString sLowerCaseFileName = fileName.toAsciiLowerCase(); + if ( fileName != sLowerCaseFileName ) + { + aFragmentPath = aFragmentPath.subView(0, nPathLen) + sLowerCaseFileName; + xInStrm = openInputStream(aFragmentPath); + } + } + + // own try/catch block for showing parser failure assertion with fragment path + if( xInStrm.is() ) try + { + rParser.setDocumentHandler(rxHandler); + rParser.parseStream(xInStrm, aFragmentPath); + return true; + } + catch( Exception& ) + { + OSL_FAIL( OStringBuffer( "XmlFilterBase::importFragment - XML parser failed in fragment '" + + OUStringToOString( aFragmentPath, RTL_TEXTENCODING_ASCII_US ) + "'" ).getStr() ); + } + } + catch( Exception& ) + { + } + return false; +} + +Reference<XDocument> XmlFilterBase::importFragment( const OUString& aFragmentPath ) +{ + Reference<XDocument> xRet; + + // path to fragment stream valid? + OSL_ENSURE( !aFragmentPath.isEmpty(), "XmlFilterBase::importFragment - empty fragment path" ); + if( aFragmentPath.isEmpty() ) + return xRet; + + // try to open the fragment stream (this may fail - do not assert) + Reference< XInputStream > xInStrm = openInputStream( aFragmentPath ); + if( !xInStrm.is() ) + return xRet; + + // binary streams (fragment extension is '.bin') currently not supported + if (aFragmentPath.endsWith(gaBinSuffix)) + return xRet; + + // try to import XML stream + try + { + // create the dom parser + Reference<XDocumentBuilder> xDomBuilder( DocumentBuilder::create( getComponentContext() ) ); + + // create DOM from fragment + xRet = xDomBuilder->parse(xInStrm); + } + catch( Exception& ) + { + } + + return xRet; +} + +bool XmlFilterBase::importFragment( const ::rtl::Reference< FragmentHandler >& rxHandler, + const Reference< XFastSAXSerializable >& rxSerializer ) +{ + if( !rxHandler.is() ) + return false; + + // try to import XML stream + try + { + rxSerializer->fastSerialize( rxHandler, + mxImpl->maFastParser.getTokenHandler(), + Sequence< StringPair >(), + NamespaceIds() ); + return true; + } + catch( Exception& ) + {} + + return false; +} + +RelationsRef XmlFilterBase::importRelations( const OUString& rFragmentPath ) +{ + // try to find cached relations + RelationsRef& rxRelations = mxImpl->maRelationsMap[ rFragmentPath ]; + if( !rxRelations ) + { + // import and cache relations + rxRelations = std::make_shared<Relations>( rFragmentPath ); + importFragment( new RelationsFragment( *this, rxRelations ) ); + } + return rxRelations; +} + +Reference< XOutputStream > XmlFilterBase::openFragmentStream( const OUString& rStreamName, const OUString& rMediaType ) +{ + Reference< XOutputStream > xOutputStream = openOutputStream( rStreamName ); + PropertySet aPropSet( xOutputStream ); + aPropSet.setProperty( PROP_MediaType, rMediaType ); + return xOutputStream; +} + +FSHelperPtr XmlFilterBase::openFragmentStreamWithSerializer( const OUString& rStreamName, const OUString& rMediaType ) +{ + const bool bWriteHeader = rMediaType.indexOf( "vml" ) < 0 || rMediaType.indexOf( "+xml" ) >= 0; + return std::make_shared<FastSerializerHelper>( openFragmentStream( rStreamName, rMediaType ), bWriteHeader ); +} + +TextFieldStack& XmlFilterBase::getTextFieldStack() const +{ + return mxImpl->maTextFieldStack; +} + +namespace { + +OUString lclAddRelation( const Reference< XRelationshipAccess >& rRelations, sal_Int32 nId, const OUString& rType, std::u16string_view rTarget, bool bExternal ) +{ + OUString sId = "rId" + OUString::number( nId ); + + Sequence< StringPair > aEntry( bExternal ? 3 : 2 ); + auto pEntry = aEntry.getArray(); + pEntry[0].First = "Type"; + pEntry[0].Second = rType; + pEntry[1].First = "Target"; + pEntry[1].Second = INetURLObject::decode(rTarget, INetURLObject::DecodeMechanism::ToIUri, RTL_TEXTENCODING_UTF8); + if( bExternal ) + { + pEntry[2].First = "TargetMode"; + pEntry[2].Second = "External"; + } + rRelations->insertRelationshipByID( sId, aEntry, true ); + + return sId; +} + +} // namespace + +OUString XmlFilterBase::addRelation( const OUString& rType, std::u16string_view rTarget ) +{ + Reference< XRelationshipAccess > xRelations( getStorage()->getXStorage(), UNO_QUERY ); + if( xRelations.is() ) + return lclAddRelation( xRelations, mnRelId ++, rType, rTarget, false/*bExternal*/ ); + + return OUString(); +} + +OUString XmlFilterBase::addRelation( const Reference< XOutputStream >& rOutputStream, const OUString& rType, std::u16string_view rTarget, bool bExternal ) +{ + sal_Int32 nId = 0; + + PropertySet aPropSet( rOutputStream ); + if( aPropSet.is() ) + aPropSet.getProperty( nId, PROP_RelId ); + else + nId = mnRelId++; + + Reference< XRelationshipAccess > xRelations( rOutputStream, UNO_QUERY ); + if( xRelations.is() ) + return lclAddRelation( xRelations, nId, rType, rTarget, bExternal ); + + return OUString(); +} + +static void +writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, std::u16string_view sValue ) +{ + pDoc->startElement(nXmlElement); + pDoc->writeEscaped( sValue ); + pDoc->endElement( nXmlElement ); +} + +static void +writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, const sal_Int32 nValue ) +{ + pDoc->startElement(nXmlElement); + pDoc->write( nValue ); + pDoc->endElement( nXmlElement ); +} + +static void +writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, const util::DateTime& rTime ) +{ + if( rTime.Year == 0 ) + return; + + if ( ( nXmlElement >> 16 ) != XML_dcterms ) + pDoc->startElement(nXmlElement); + else + pDoc->startElement(nXmlElement, FSNS(XML_xsi, XML_type), "dcterms:W3CDTF"); + + char pStr[200]; + snprintf( pStr, sizeof( pStr ), "%d-%02d-%02dT%02d:%02d:%02dZ", + rTime.Year, rTime.Month, rTime.Day, + rTime.Hours, rTime.Minutes, rTime.Seconds ); + + pDoc->write( pStr ); + + pDoc->endElement( nXmlElement ); +} + +static void +writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, const Sequence< OUString >& aItems ) +{ + if( !aItems.hasElements() ) + return; + + OUStringBuffer sRep; + // tdf#143175 - join elements including a delimiter using a standard iterator + ::comphelper::intersperse(aItems.begin(), aItems.end(), + ::comphelper::OUStringBufferAppender(sRep), OUString(" ")); + + writeElement( pDoc, nXmlElement, sRep.makeStringAndClear() ); +} + +static void +writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, const LanguageTag& rLanguageTag ) +{ + // dc:language, Dublin Core recommends "such as RFC 4646", which is BCP 47 + // and obsoleted by RFC 5646, see + // http://dublincore.org/documents/dcmi-terms/#terms-language + // http://dublincore.org/documents/dcmi-terms/#elements-language + writeElement( pDoc, nXmlElement, rLanguageTag.getBcp47MS() ); +} + +static void +writeCoreProperties( XmlFilterBase& rSelf, const Reference< XDocumentProperties >& xProperties ) +{ + OUString sValue; + if( rSelf.getVersion() == oox::core::ISOIEC_29500_2008 ) + { + // The lowercase "officedocument" is intentional and according to the spec + // (although most other places are written "officeDocument") + sValue = "http://schemas.openxmlformats.org/officedocument/2006/relationships/metadata/core-properties"; + } + else + sValue = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"; + + rSelf.addRelation( sValue, u"docProps/core.xml" ); + FSHelperPtr pCoreProps = rSelf.openFragmentStreamWithSerializer( + "docProps/core.xml", + "application/vnd.openxmlformats-package.core-properties+xml" ); + pCoreProps->startElementNS( XML_cp, XML_coreProperties, + FSNS(XML_xmlns, XML_cp), rSelf.getNamespaceURL(OOX_NS(packageMetaCorePr)), + FSNS(XML_xmlns, XML_dc), rSelf.getNamespaceURL(OOX_NS(dc)), + FSNS(XML_xmlns, XML_dcterms), rSelf.getNamespaceURL(OOX_NS(dcTerms)), + FSNS(XML_xmlns, XML_dcmitype), rSelf.getNamespaceURL(OOX_NS(dcmiType)), + FSNS(XML_xmlns, XML_xsi), rSelf.getNamespaceURL(OOX_NS(xsi))); + + uno::Reference<beans::XPropertyAccess> xUserDefinedProperties(xProperties->getUserDefinedProperties(), uno::UNO_QUERY); + comphelper::SequenceAsHashMap aUserDefinedProperties(xUserDefinedProperties->getPropertyValues()); + comphelper::SequenceAsHashMap::iterator it; + + it = aUserDefinedProperties.find("OOXMLCorePropertyCategory"); + if (it != aUserDefinedProperties.end()) + { + OUString aValue; + if (it->second >>= aValue) + writeElement( pCoreProps, FSNS( XML_cp, XML_category ), aValue ); + } + + it = aUserDefinedProperties.find("OOXMLCorePropertyContentStatus"); + if (it != aUserDefinedProperties.end()) + { + OUString aValue; + if (it->second >>= aValue) + writeElement( pCoreProps, FSNS( XML_cp, XML_contentStatus ), aValue ); + } + + it = aUserDefinedProperties.find("OOXMLCorePropertyContentType"); + if (it != aUserDefinedProperties.end()) + { + OUString aValue; + if (it->second >>= aValue) + writeElement( pCoreProps, FSNS( XML_cp, XML_contentType ), aValue ); + } + writeElement( pCoreProps, FSNS( XML_dcterms, XML_created ), xProperties->getCreationDate() ); + writeElement( pCoreProps, FSNS( XML_dc, XML_creator ), xProperties->getAuthor() ); + writeElement( pCoreProps, FSNS( XML_dc, XML_description ), xProperties->getDescription() ); + + it = aUserDefinedProperties.find("OOXMLCorePropertyIdentifier"); + if (it != aUserDefinedProperties.end()) + { + OUString aValue; + if (it->second >>= aValue) + writeElement( pCoreProps, FSNS( XML_dc, XML_identifier ), aValue ); + } + writeElement( pCoreProps, FSNS( XML_cp, XML_keywords ), xProperties->getKeywords() ); + writeElement( pCoreProps, FSNS( XML_dc, XML_language ), LanguageTag( xProperties->getLanguage()) ); + writeElement( pCoreProps, FSNS( XML_cp, XML_lastModifiedBy ), xProperties->getModifiedBy() ); + writeElement( pCoreProps, FSNS( XML_cp, XML_lastPrinted ), xProperties->getPrintDate() ); + writeElement( pCoreProps, FSNS( XML_dcterms, XML_modified ), xProperties->getModificationDate() ); + writeElement( pCoreProps, FSNS( XML_cp, XML_revision ), xProperties->getEditingCycles() ); + writeElement( pCoreProps, FSNS( XML_dc, XML_subject ), xProperties->getSubject() ); + writeElement( pCoreProps, FSNS( XML_dc, XML_title ), xProperties->getTitle() ); + + it = aUserDefinedProperties.find("OOXMLCorePropertyVersion"); + if (it != aUserDefinedProperties.end()) + { + OUString aValue; + if (it->second >>= aValue) + writeElement( pCoreProps, FSNS( XML_cp, XML_version ), aValue ); + } + + pCoreProps->endElementNS( XML_cp, XML_coreProperties ); +} + +static void +writeAppProperties( XmlFilterBase& rSelf, const Reference< XDocumentProperties >& xProperties ) +{ + rSelf.addRelation( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", + u"docProps/app.xml" ); + FSHelperPtr pAppProps = rSelf.openFragmentStreamWithSerializer( + "docProps/app.xml", + "application/vnd.openxmlformats-officedocument.extended-properties+xml" ); + pAppProps->startElement( XML_Properties, + XML_xmlns, rSelf.getNamespaceURL(OOX_NS(officeExtPr)), + FSNS(XML_xmlns, XML_vt), rSelf.getNamespaceURL(OOX_NS(officeDocPropsVT))); + + uno::Reference<beans::XPropertyAccess> xUserDefinedProperties(xProperties->getUserDefinedProperties(), uno::UNO_QUERY); + comphelper::SequenceAsHashMap aUserDefinedProperties(xUserDefinedProperties->getPropertyValues()); + comphelper::SequenceAsHashMap::iterator it; + + writeElement( pAppProps, XML_Template, xProperties->getTemplateName() ); + + it = aUserDefinedProperties.find("Manager"); + if (it != aUserDefinedProperties.end()) + { + OUString aValue; + if (it->second >>= aValue) + writeElement( pAppProps, XML_Manager, aValue ); + } + +#ifdef OOXTODO + writeElement( pAppProps, XML_PresentationFormat, "presentation format" ); + writeElement( pAppProps, XML_Lines, "lines" ); + writeElement( pAppProps, XML_Slides, "slides" ); + writeElement( pAppProps, XML_Notes, "notes" ); +#endif /* def OOXTODO */ + // EditingDuration is in seconds, TotalTime is in minutes. + writeElement( pAppProps, XML_TotalTime, xProperties->getEditingDuration() / 60 ); +#ifdef OOXTODO + writeElement( pAppProps, XML_HiddenSlides, "hidden slides" ); + writeElement( pAppProps, XML_MMClips, "mm clips" ); + writeElement( pAppProps, XML_ScaleCrop, "scale crop" ); + writeElement( pAppProps, XML_HeadingPairs, "heading pairs" ); + writeElement( pAppProps, XML_TitlesOfParts, "titles of parts" ); + writeElement( pAppProps, XML_LinksUpToDate, "links up-to-date" ); + writeElement( pAppProps, XML_SharedDoc, "shared doc" ); + writeElement( pAppProps, XML_HLinks, "hlinks" ); + writeElement( pAppProps, XML_HyperlinksChanged, "hyperlinks changed" ); + writeElement( pAppProps, XML_DigSig, "digital signature" ); +#endif /* def OOXTODO */ + writeElement( pAppProps, XML_Application, utl::DocInfoHelper::GetGeneratorString() ); + + it = aUserDefinedProperties.find("HyperlinkBase"); + if (it != aUserDefinedProperties.end()) + { + OUString aValue; + if (it->second >>= aValue) + writeElement( pAppProps, XML_HyperlinkBase, aValue ); + } + // AppVersion specifies the version of the application which produced document + // It is strictly connected with MS Office versions: + // * 12: [Office 2007] [LO < 7.0] + // * 14: [Office 2010] + // * 15: [Office 2013/2016/2019] [LO >= 7.0] + // The LibreOffice is application on 2013/2016/2019 level + writeElement( pAppProps, XML_AppVersion, u"15.0000" ); + + // OOXTODO Calculate DocSecurity value based on security (password, read-only etc.) + it = aUserDefinedProperties.find("DocSecurity"); + if (it != aUserDefinedProperties.end()) + { + sal_Int32 nValue; + if (it->second >>= nValue) + writeElement( pAppProps, XML_DocSecurity, nValue ); + } + + comphelper::SequenceAsHashMap aStats = xProperties->getDocumentStatistics(); + sal_Int32 nValue = 0; + + it = aStats.find("PageCount"); + if (it != aStats.end()) + { + if (it->second >>= nValue) + writeElement(pAppProps, XML_Pages, nValue); + } + + it = aStats.find("WordCount"); + if (it != aStats.end()) + { + if (it->second >>= nValue) + writeElement(pAppProps, XML_Words, nValue); + } + + it = aStats.find("NonWhitespaceCharacterCount"); + if (it != aStats.end()) + { + if (it->second >>= nValue) + writeElement(pAppProps, XML_Characters, nValue); + } + + it = aStats.find("CharacterCount"); + if (it != aStats.end()) + { + if (it->second >>= nValue) + writeElement(pAppProps, XML_CharactersWithSpaces, nValue); + } + + it = aStats.find("ParagraphCount"); + if (it != aStats.end()) + { + if (it->second >>= nValue) + writeElement(pAppProps, XML_Paragraphs, nValue); + } + + it = aUserDefinedProperties.find("Company"); + if (it != aUserDefinedProperties.end()) + { + OUString aValue; + if (it->second >>= aValue) + writeElement(pAppProps, XML_Company, aValue); + } + + pAppProps->endElement( XML_Properties ); +} + +static void +writeCustomProperties( XmlFilterBase& rSelf, const Reference< XDocumentProperties >& xProperties, bool bSecurityOptOpenReadOnly ) +{ + uno::Reference<beans::XPropertyAccess> xUserDefinedProperties( xProperties->getUserDefinedProperties(), uno::UNO_QUERY ); + auto aprop = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(xUserDefinedProperties->getPropertyValues()); + sal_Int32 nbCustomProperties = aprop.size(); + // tdf#89791 : if no custom properties, no need to add docProps/custom.x + // tdf#107690: except the case of read-only documents, because that + // is handled by the _MarkAsFinal custom property in MSO. + if (!nbCustomProperties && !bSecurityOptOpenReadOnly) + return; + + if (bSecurityOptOpenReadOnly) + { + PropertyValue aPropertyValue; + // MSO custom property for read-only documents + aPropertyValue.Name = "_MarkAsFinal"; + aPropertyValue.Value <<= true; + aprop.push_back(aPropertyValue); + } + + rSelf.addRelation( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties", + u"docProps/custom.xml" ); + FSHelperPtr pAppProps = rSelf.openFragmentStreamWithSerializer( + "docProps/custom.xml", + "application/vnd.openxmlformats-officedocument.custom-properties+xml" ); + pAppProps->startElement( XML_Properties, + XML_xmlns, rSelf.getNamespaceURL(OOX_NS(officeCustomPr)), + FSNS(XML_xmlns, XML_vt), rSelf.getNamespaceURL(OOX_NS(officeDocPropsVT))); + + size_t nIndex = 0; + for (const auto& rProp : aprop) + { + if ( !rProp.Name.isEmpty() ) + { + // Skip storing these values in Custom Properties as it will be stored in Core/Extended Properties + if (( rProp.Name == "OOXMLCorePropertyCategory" ) || // stored in cp:category + ( rProp.Name == "OOXMLCorePropertyContentStatus" ) || // stored in cp:contentStatus + ( rProp.Name == "OOXMLCorePropertyContentType" ) || // stored in cp:contentType + ( rProp.Name == "OOXMLCorePropertyIdentifier" ) || // stored in dc:identifier + ( rProp.Name == "OOXMLCorePropertyVersion" ) || // stored in cp:version + ( rProp.Name == "HyperlinkBase" ) || // stored in Extended File Properties + ( rProp.Name == "AppVersion" ) || // stored in Extended File Properties + ( rProp.Name == "DocSecurity" ) || // stored in Extended File Properties + ( rProp.Name == "Manager" ) || // stored in Extended File Properties + ( rProp.Name == "Company" )) // stored in Extended File Properties + continue; + + // pid starts from 2 not from 1 as MS supports pid from 2 + pAppProps->startElement( XML_property , + XML_fmtid, "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}", + XML_pid, OString::number(nIndex + 2), + XML_name, rProp.Name); + + switch ( rProp.Value.getValueTypeClass() ) + { + case TypeClass_STRING: + { + OUString aValue; + rProp.Value >>= aValue; + writeElement( pAppProps, FSNS( XML_vt, XML_lpwstr ), aValue ); + } + break; + case TypeClass_BOOLEAN: + { + bool val = *o3tl::forceAccess<bool>(rProp.Value); + writeElement( pAppProps, FSNS( XML_vt, XML_bool ), val ? 1 : 0); + } + break; + case TypeClass_DOUBLE: + { + double num = {}; // spurious -Werror=maybe-uninitialized + if ( rProp.Value >>= num ) + { + // r8 - 8-byte real number + writeElement( pAppProps, FSNS( XML_vt, XML_r8 ), OUString::number(num) ); + } + } + break; + default: + { + double num = {}; // spurious -Werror=maybe-uninitialized + util::Date aDate; + util::Duration aDuration; + util::DateTime aDateTime; + if ( rProp.Value >>= num ) + { + // i4 - 4-byte signed integer + writeElement( pAppProps, FSNS( XML_vt, XML_i4 ), num ); + } + else if ( rProp.Value >>= aDate ) + { + aDateTime = util::DateTime( 0, 0 , 0, 0, aDate.Day, aDate.Month, aDate.Year, true ); + writeElement( pAppProps, FSNS( XML_vt, XML_filetime ), aDateTime); + } + else if ( rProp.Value >>= aDuration ) + { + OUStringBuffer buf; + ::sax::Converter::convertDuration( buf, aDuration ); + OUString aDurationStr = buf.makeStringAndClear(); + writeElement( pAppProps, FSNS( XML_vt, XML_lpwstr ), aDurationStr ); + } + else if ( rProp.Value >>= aDateTime ) + writeElement( pAppProps, FSNS( XML_vt, XML_filetime ), aDateTime ); + else + //no other options + OSL_FAIL( "XMLFilterBase::writeCustomProperties unsupported value type!" ); + } + break; + } + pAppProps->endElement( XML_property ); + } + ++nIndex; + } + pAppProps->endElement( XML_Properties ); +} + +void XmlFilterBase::exportDocumentProperties( const Reference< XDocumentProperties >& xProperties, bool bSecurityOptOpenReadOnly ) +{ + if( xProperties.is() ) + { + writeCoreProperties( *this, xProperties ); + writeAppProperties( *this, xProperties ); + writeCustomProperties( *this, xProperties, bSecurityOptOpenReadOnly ); + } +} + +// protected ------------------------------------------------------------------ + +Reference< XInputStream > XmlFilterBase::implGetInputStream( MediaDescriptor& rMediaDesc ) const +{ + /* Get the input stream directly from the media descriptor, or decrypt the + package again. The latter is needed e.g. when the document is reloaded. + All this is implemented in the detector service. */ + rtl::Reference< FilterDetect > xDetector( new FilterDetect( getComponentContext() ) ); + return xDetector->extractUnencryptedPackage( rMediaDesc ); +} + +Reference<XStream> XmlFilterBase::implGetOutputStream( MediaDescriptor& rMediaDescriptor ) const +{ + const Sequence< NamedValue > aMediaEncData = rMediaDescriptor.getUnpackedValueOrDefault( + MediaDescriptor::PROP_ENCRYPTIONDATA, + Sequence< NamedValue >() ); + + if (aMediaEncData.getLength() == 0) + { + return FilterBase::implGetOutputStream( rMediaDescriptor ); + } + else // We need to encrypt the stream so create a memory stream + { + Reference< XComponentContext > xContext = getComponentContext(); + return Reference< XStream > ( + xContext->getServiceManager()->createInstanceWithContext("com.sun.star.comp.MemoryStream", xContext), + uno::UNO_QUERY_THROW ); + } +} + +bool XmlFilterBase::implFinalizeExport( MediaDescriptor& rMediaDescriptor ) +{ + bool bRet = true; + + const Sequence< NamedValue > aMediaEncData = rMediaDescriptor.getUnpackedValueOrDefault( + MediaDescriptor::PROP_ENCRYPTIONDATA, + Sequence< NamedValue >() ); + + if (aMediaEncData.getLength()) + { + commitStorage(); + + Reference< XStream> xDocumentStream (FilterBase::implGetOutputStream(rMediaDescriptor)); + oox::ole::OleStorage aOleStorage( getComponentContext(), xDocumentStream, true ); + crypto::DocumentEncryption encryptor( getComponentContext(), getMainDocumentStream(), aOleStorage, aMediaEncData ); + bRet = encryptor.encrypt(); + if (bRet) + aOleStorage.commit(); + } + + return bRet; +} + +// private -------------------------------------------------------------------- + +StorageRef XmlFilterBase::implCreateStorage( const Reference< XInputStream >& rxInStream ) const +{ + return std::make_shared<ZipStorage>( getComponentContext(), rxInStream ); +} + +StorageRef XmlFilterBase::implCreateStorage( const Reference< XStream >& rxOutStream ) const +{ + return std::make_shared<ZipStorage>( getComponentContext(), rxOutStream ); +} + +bool XmlFilterBase::isMSO2007Document() const +{ + return mbMSO2007; +} + +bool XmlFilterBase::isMSODocument() const +{ + return mbMSO; +} + +void XmlFilterBase::setMissingExtDrawing() +{ + mbMissingExtDrawing = true; +} + +void XmlFilterBase::setDiagramFontHeights(NamedShapePairs* pDiagramFontHeights) +{ + mxImpl->mpDiagramFontHeights = pDiagramFontHeights; +} + +NamedShapePairs* XmlFilterBase::getDiagramFontHeights() { return mxImpl->mpDiagramFontHeights; } + +OUString XmlFilterBase::getNamespaceURL(sal_Int32 nNSID) const +{ + auto itr = mxImpl->mrNamespaceMap.maTransitionalNamespaceMap.find(nNSID); + if (itr == mxImpl->mrNamespaceMap.maTransitionalNamespaceMap.end()) + { + SAL_WARN("oox", "missing namespace in the namespace map for : " << nNSID); + return OUString(); + } + + return itr->second; +} + +void XmlFilterBase::importCustomFragments(css::uno::Reference<css::embed::XStorage> const & xDocumentStorage) +{ + Reference<XRelationshipAccess> xRelations(xDocumentStorage, UNO_QUERY); + if (!xRelations.is()) + return; + + const uno::Sequence<uno::Sequence<beans::StringPair>> aSeqs = xRelations->getAllRelationships(); + + std::vector<StreamDataSequence> aCustomFragments; + std::vector<OUString> aCustomFragmentTypes; + std::vector<OUString> aCustomFragmentTargets; + for (const uno::Sequence<beans::StringPair>& aSeq : aSeqs) + { + OUString sType; + OUString sTarget; + for (const beans::StringPair& aPair : aSeq) + { + if (aPair.First == "Target") + sTarget = aPair.Second; + else if (aPair.First == "Type") + sType = aPair.Second; + } + + // Preserve non-standard (i.e. custom) entries. + if (!sType.match("http://schemas.openxmlformats.org") // OOXML/ECMA Transitional + && !sType.match("http://purl.oclc.org")) // OOXML Strict + { + StreamDataSequence aDataSeq; + if (importBinaryData(aDataSeq, sTarget)) + { + aCustomFragments.emplace_back(aDataSeq); + aCustomFragmentTypes.emplace_back(sType); + aCustomFragmentTargets.emplace_back(sTarget); + } + } + } + + // Adding the saved custom xml DOM + comphelper::SequenceAsHashMap aGrabBagProperties; + aGrabBagProperties["OOXCustomFragments"] <<= comphelper::containerToSequence(aCustomFragments); + aGrabBagProperties["OOXCustomFragmentTypes"] <<= comphelper::containerToSequence(aCustomFragmentTypes); + aGrabBagProperties["OOXCustomFragmentTargets"] <<= comphelper::containerToSequence(aCustomFragmentTargets); + + std::vector<uno::Reference<xml::dom::XDocument>> aCustomXmlDomList; + std::vector<uno::Reference<xml::dom::XDocument>> aCustomXmlDomPropsList; + //FIXME: Ideally, we should get these the relations, but it seems that is not consistently set. + // In some cases it's stored in the workbook relationships, which is unexpected. So we discover them directly. + for (int i = 1; ; ++i) + { + Reference<XDocument> xCustDoc = importFragment("customXml/item" + OUString::number(i) + ".xml"); + Reference<XDocument> xCustDocProps = importFragment("customXml/itemProps" + OUString::number(i) + ".xml"); + if (xCustDoc && xCustDocProps) + { + aCustomXmlDomList.emplace_back(xCustDoc); + aCustomXmlDomPropsList.emplace_back(xCustDocProps); + } + else + break; + } + + // Adding the saved custom xml DOM + aGrabBagProperties["OOXCustomXml"] <<= comphelper::containerToSequence(aCustomXmlDomList); + aGrabBagProperties["OOXCustomXmlProps"] <<= comphelper::containerToSequence(aCustomXmlDomPropsList); + + // Save the [Content_Types].xml after parsing. + uno::Sequence<uno::Sequence<beans::StringPair>> aContentTypeInfo; + uno::Reference<io::XInputStream> xInputStream = openInputStream("[Content_Types].xml"); + if (xInputStream.is()) + aContentTypeInfo = comphelper::OFOPXMLHelper::ReadContentTypeSequence(xInputStream, getComponentContext()); + + aGrabBagProperties["OOXContentTypes"] <<= aContentTypeInfo; + + Reference<XComponent> xModel = getModel(); + oox::core::XmlFilterBase::putPropertiesToDocumentGrabBag(xModel, aGrabBagProperties); +} + +void XmlFilterBase::exportCustomFragments() +{ + Reference<XComponent> xModel = getModel(); + uno::Reference<beans::XPropertySet> xPropSet(xModel, uno::UNO_QUERY_THROW); + + uno::Reference<beans::XPropertySetInfo> xPropSetInfo = xPropSet->getPropertySetInfo(); + if (!xPropSetInfo->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG)) + return; + + uno::Sequence<uno::Reference<xml::dom::XDocument>> customXmlDomlist; + uno::Sequence<uno::Reference<xml::dom::XDocument>> customXmlDomPropslist; + uno::Sequence<StreamDataSequence> customFragments; + uno::Sequence<OUString> customFragmentTypes; + uno::Sequence<OUString> customFragmentTargets; + uno::Sequence<uno::Sequence<beans::StringPair>> aContentTypes; + + uno::Sequence<beans::PropertyValue> propList; + xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= propList; + for (const auto& rProp : std::as_const(propList)) + { + const OUString propName = rProp.Name; + if (propName == "OOXCustomXml") + { + rProp.Value >>= customXmlDomlist; + } + else if (propName == "OOXCustomXmlProps") + { + rProp.Value >>= customXmlDomPropslist; + } + else if (propName == "OOXCustomFragments") + { + rProp.Value >>= customFragments; + } + else if (propName == "OOXCustomFragmentTypes") + { + rProp.Value >>= customFragmentTypes; + } + else if (propName == "OOXCustomFragmentTargets") + { + rProp.Value >>= customFragmentTargets; + } + else if (propName == "OOXContentTypes") + { + rProp.Value >>= aContentTypes; + } + } + + // Expect customXmlDomPropslist.getLength() == customXmlDomlist.getLength(). + for (sal_Int32 j = 0; j < customXmlDomlist.getLength(); j++) + { + uno::Reference<xml::dom::XDocument> customXmlDom = customXmlDomlist[j]; + uno::Reference<xml::dom::XDocument> customXmlDomProps = customXmlDomPropslist[j]; + const OUString fragmentPath = "customXml/item" + OUString::number(j+1) + ".xml"; + if (customXmlDom.is()) + { + addRelation(oox::getRelationship(Relationship::CUSTOMXML), OUStringConcatenation("../" + fragmentPath)); + + uno::Reference<xml::sax::XSAXSerializable> serializer(customXmlDom, uno::UNO_QUERY); + uno::Reference<xml::sax::XWriter> writer = xml::sax::Writer::create(comphelper::getProcessComponentContext()); + writer->setOutputStream(openFragmentStream(fragmentPath, "application/xml")); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + } + + if (customXmlDomProps.is()) + { + uno::Reference<xml::sax::XSAXSerializable> serializer(customXmlDomProps, uno::UNO_QUERY); + uno::Reference<xml::sax::XWriter> writer = xml::sax::Writer::create(comphelper::getProcessComponentContext()); + writer->setOutputStream(openFragmentStream("customXml/itemProps"+OUString::number(j+1)+".xml", + "application/vnd.openxmlformats-officedocument.customXmlProperties+xml")); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // Adding itemprops's relationship entry to item.xml.rels file + addRelation(openFragmentStream(fragmentPath, "application/xml"), + oox::getRelationship(Relationship::CUSTOMXMLPROPS), + OUStringConcatenation("itemProps"+OUString::number(j+1)+".xml")); + } + } + + // Expect customFragments.getLength() == customFragmentTypes.getLength() == customFragmentTargets.getLength(). + for (sal_Int32 j = 0; j < customFragments.getLength(); j++) + { + addRelation(customFragmentTypes[j], customFragmentTargets[j]); + const OUString aFilename = customFragmentTargets[j]; + Reference<XOutputStream> xOutStream = openOutputStream(aFilename); + if (xOutStream.is()) + { + xOutStream->writeBytes(customFragments[j]); + uno::Reference<XPropertySet> xProps(xOutStream, uno::UNO_QUERY); + if (xProps.is()) + { + const OUString aType = comphelper::OFOPXMLHelper::GetContentTypeByName(aContentTypes, aFilename); + const OUString aContentType = (aType.getLength() ? aType : OUString("application/octet-stream")); + xProps->setPropertyValue("MediaType", uno::Any(aContentType)); + } + } + } +} + +} // namespace oox::core + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |