diff options
Diffstat (limited to 'comphelper/source/xml/ofopxmlhelper.cxx')
-rw-r--r-- | comphelper/source/xml/ofopxmlhelper.cxx | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/comphelper/source/xml/ofopxmlhelper.cxx b/comphelper/source/xml/ofopxmlhelper.cxx new file mode 100644 index 000000000..46785b35c --- /dev/null +++ b/comphelper/source/xml/ofopxmlhelper.cxx @@ -0,0 +1,491 @@ +/* -*- 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 <comphelper/ofopxmlhelper.hxx> +#include <comphelper/attributelist.hxx> + +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <vector> + +#define RELATIONINFO_FORMAT 0 +#define CONTENTTYPE_FORMAT 1 +#define FORMAT_MAX_ID CONTENTTYPE_FORMAT + +using namespace ::com::sun::star; + +namespace comphelper { + +namespace { + +// this helper class is designed to allow to parse ContentType- and Relationship-related information from OfficeOpenXML format +class OFOPXMLHelper_Impl + : public cppu::WeakImplHelper< css::xml::sax::XDocumentHandler > +{ + sal_uInt16 const m_nFormat; // which format to parse + + css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > m_aResultSeq; + std::vector< OUString > m_aElementsSeq; // stack of elements being parsed + + +public: + css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > const & GetParsingResult() const; + + explicit OFOPXMLHelper_Impl( sal_uInt16 nFormat ); // must not be created directly + + // XDocumentHandler + virtual void SAL_CALL startDocument() override; + virtual void SAL_CALL endDocument() override; + virtual void SAL_CALL startElement( const OUString& aName, const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs ) override; + virtual void SAL_CALL endElement( const OUString& aName ) override; + virtual void SAL_CALL characters( const OUString& aChars ) override; + virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) override; + virtual void SAL_CALL processingInstruction( const OUString& aTarget, const OUString& aData ) override; + virtual void SAL_CALL setDocumentLocator( const css::uno::Reference< css::xml::sax::XLocator >& xLocator ) override; +}; + +} + +namespace OFOPXMLHelper { + +/// @throws css::uno::Exception +static uno::Sequence<uno::Sequence< beans::StringPair>> ReadSequence_Impl( + const uno::Reference<io::XInputStream>& xInStream, + const OUString& aStringID, sal_uInt16 nFormat, + const uno::Reference<uno::XComponentContext>& xContext); + +uno::Sequence< uno::Sequence< beans::StringPair > > ReadRelationsInfoSequence( + const uno::Reference< io::XInputStream >& xInStream, + const OUString & aStreamName, + const uno::Reference< uno::XComponentContext >& rContext ) +{ + OUString aStringID = "_rels/" + aStreamName; + return ReadSequence_Impl( xInStream, aStringID, RELATIONINFO_FORMAT, rContext ); +} + + +uno::Sequence< uno::Sequence< beans::StringPair > > ReadContentTypeSequence( + const uno::Reference< io::XInputStream >& xInStream, + const uno::Reference< uno::XComponentContext >& rContext ) +{ + OUString aStringID = "[Content_Types].xml"; + return ReadSequence_Impl( xInStream, aStringID, CONTENTTYPE_FORMAT, rContext ); +} + +OUString GetContentTypeByName( + const css::uno::Sequence<css::uno::Sequence<css::beans::StringPair>>& rContentTypes, + const OUString& rFilename) +{ + if (rContentTypes.getLength() < 2) + { + return OUString(); + } + + const uno::Sequence<beans::StringPair>& rDefaults = rContentTypes[0]; + const uno::Sequence<beans::StringPair>& rOverrides = rContentTypes[1]; + + // Find the extension and use it to get the type. + const sal_Int32 nDotOffset = rFilename.lastIndexOf('.'); + const OUString aExt = (nDotOffset >= 0 ? rFilename.copy(nDotOffset + 1) : rFilename); // Skip the dot. + + const std::vector<OUString> aNames = { aExt, "/" + rFilename }; + for (const OUString& aName : aNames) + { + const auto it1 = std::find_if(rOverrides.begin(), rOverrides.end(), [&aName](const beans::StringPair& rPair) + { return rPair.First == aName; }); + if (it1 != rOverrides.end()) + return it1->Second; + + const auto it2 = std::find_if(rDefaults.begin(), rDefaults.end(), [&aName](const beans::StringPair& rPair) + { return rPair.First == aName; }); + if (it2 != rDefaults.end()) + return it2->Second; + } + + return OUString(); +} + +void WriteRelationsInfoSequence( + const uno::Reference< io::XOutputStream >& xOutStream, + const uno::Sequence< uno::Sequence< beans::StringPair > >& aSequence, + const uno::Reference< uno::XComponentContext >& rContext ) +{ + if ( !xOutStream.is() ) + throw uno::RuntimeException(); + + uno::Reference< css::xml::sax::XWriter > xWriter = css::xml::sax::Writer::create(rContext); + + xWriter->setOutputStream( xOutStream ); + + OUString aRelListElement( "Relationships" ); + OUString aRelElement( "Relationship" ); + OUString aCDATAString( "CDATA" ); + OUString aWhiteSpace( " " ); + + // write the namespace + AttributeList* pRootAttrList = new AttributeList; + uno::Reference< css::xml::sax::XAttributeList > xRootAttrList( pRootAttrList ); + pRootAttrList->AddAttribute( + "xmlns", + aCDATAString, + "http://schemas.openxmlformats.org/package/2006/relationships" ); + + xWriter->startDocument(); + xWriter->startElement( aRelListElement, xRootAttrList ); + + for ( sal_Int32 nInd = 0; nInd < aSequence.getLength(); nInd++ ) + { + AttributeList *pAttrList = new AttributeList; + uno::Reference< css::xml::sax::XAttributeList > xAttrList( pAttrList ); + for( sal_Int32 nSecInd = 0; nSecInd < aSequence[nInd].getLength(); nSecInd++ ) + { + if ( !(aSequence[nInd][nSecInd].First == "Id" + || aSequence[nInd][nSecInd].First == "Type" + || aSequence[nInd][nSecInd].First == "TargetMode" + || aSequence[nInd][nSecInd].First == "Target") ) + { + // TODO/LATER: should the extensions be allowed? + throw lang::IllegalArgumentException(); + } + pAttrList->AddAttribute( aSequence[nInd][nSecInd].First, aCDATAString, aSequence[nInd][nSecInd].Second ); + } + + xWriter->startElement( aRelElement, xAttrList ); + xWriter->ignorableWhitespace( aWhiteSpace ); + xWriter->endElement( aRelElement ); + } + + xWriter->ignorableWhitespace( aWhiteSpace ); + xWriter->endElement( aRelListElement ); + xWriter->endDocument(); +} + + +void WriteContentSequence( + const uno::Reference< io::XOutputStream >& xOutStream, + const uno::Sequence< beans::StringPair >& aDefaultsSequence, + const uno::Sequence< beans::StringPair >& aOverridesSequence, + const uno::Reference< uno::XComponentContext >& rContext ) +{ + if ( !xOutStream.is() ) + throw uno::RuntimeException(); + + uno::Reference< css::xml::sax::XWriter > xWriter = css::xml::sax::Writer::create(rContext); + + xWriter->setOutputStream( xOutStream ); + + static const OUString aTypesElement("Types"); + static const OUString aDefaultElement("Default"); + static const OUString aOverrideElement("Override"); + static const OUString aContentTypeAttr("ContentType"); + static const OUString aCDATAString("CDATA"); + static const OUString aWhiteSpace(" "); + + // write the namespace + AttributeList* pRootAttrList = new AttributeList; + uno::Reference< css::xml::sax::XAttributeList > xRootAttrList( pRootAttrList ); + pRootAttrList->AddAttribute( + "xmlns", + aCDATAString, + "http://schemas.openxmlformats.org/package/2006/content-types" ); + + xWriter->startDocument(); + xWriter->startElement( aTypesElement, xRootAttrList ); + + for ( sal_Int32 nInd = 0; nInd < aDefaultsSequence.getLength(); nInd++ ) + { + AttributeList *pAttrList = new AttributeList; + uno::Reference< css::xml::sax::XAttributeList > xAttrList( pAttrList ); + pAttrList->AddAttribute( "Extension", aCDATAString, aDefaultsSequence[nInd].First ); + pAttrList->AddAttribute( aContentTypeAttr, aCDATAString, aDefaultsSequence[nInd].Second ); + + xWriter->startElement( aDefaultElement, xAttrList ); + xWriter->ignorableWhitespace( aWhiteSpace ); + xWriter->endElement( aDefaultElement ); + } + + for ( sal_Int32 nInd = 0; nInd < aOverridesSequence.getLength(); nInd++ ) + { + AttributeList *pAttrList = new AttributeList; + uno::Reference< css::xml::sax::XAttributeList > xAttrList( pAttrList ); + pAttrList->AddAttribute( "PartName", aCDATAString, aOverridesSequence[nInd].First ); + pAttrList->AddAttribute( aContentTypeAttr, aCDATAString, aOverridesSequence[nInd].Second ); + + xWriter->startElement( aOverrideElement, xAttrList ); + xWriter->ignorableWhitespace( aWhiteSpace ); + xWriter->endElement( aOverrideElement ); + } + + xWriter->ignorableWhitespace( aWhiteSpace ); + xWriter->endElement( aTypesElement ); + xWriter->endDocument(); + +} + +uno::Sequence< uno::Sequence< beans::StringPair > > ReadSequence_Impl( + const uno::Reference< io::XInputStream >& xInStream, + const OUString& aStringID, sal_uInt16 nFormat, + const uno::Reference< uno::XComponentContext >& rContext ) +{ + if ( !rContext.is() || !xInStream.is() || nFormat > FORMAT_MAX_ID ) + throw uno::RuntimeException(); + + uno::Reference< css::xml::sax::XParser > xParser = css::xml::sax::Parser::create( rContext ); + + OFOPXMLHelper_Impl *const pHelper = new OFOPXMLHelper_Impl( nFormat ); + uno::Reference< css::xml::sax::XDocumentHandler > xHelper( static_cast< css::xml::sax::XDocumentHandler* >( pHelper ) ); + css::xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInStream; + aParserInput.sSystemId = aStringID; + xParser->setDocumentHandler( xHelper ); + xParser->parseStream( aParserInput ); + xParser->setDocumentHandler( uno::Reference < css::xml::sax::XDocumentHandler > () ); + + return pHelper->GetParsingResult(); +} + +} // namespace OFOPXMLHelper + +// Relations info related strings +static OUString const g_aRelListElement("Relationships"); +static OUString const g_aRelElement( "Relationship" ); +static OUString const g_aIDAttr( "Id" ); +static OUString const g_aTypeAttr( "Type" ); +static OUString const g_aTargetModeAttr( "TargetMode" ); +static OUString const g_aTargetAttr( "Target" ); + +// ContentType related strings +static OUString const g_aTypesElement( "Types" ); +static OUString const g_aDefaultElement( "Default" ); +static OUString const g_aOverrideElement( "Override" ); +static OUString const g_aExtensionAttr( "Extension" ); +static OUString const g_aPartNameAttr( "PartName" ); +static OUString const g_aContentTypeAttr( "ContentType" ); + +OFOPXMLHelper_Impl::OFOPXMLHelper_Impl( sal_uInt16 nFormat ) +: m_nFormat( nFormat ) +{ +} + +uno::Sequence< uno::Sequence< beans::StringPair > > const & OFOPXMLHelper_Impl::GetParsingResult() const +{ + if ( !m_aElementsSeq.empty() ) + throw uno::RuntimeException(); // the parsing has still not finished! + + return m_aResultSeq; +} + + +void SAL_CALL OFOPXMLHelper_Impl::startDocument() +{ +} + + +void SAL_CALL OFOPXMLHelper_Impl::endDocument() +{ +} + + +void SAL_CALL OFOPXMLHelper_Impl::startElement( const OUString& aName, const uno::Reference< css::xml::sax::XAttributeList >& xAttribs ) +{ + if ( m_nFormat == RELATIONINFO_FORMAT ) + { + if ( aName == g_aRelListElement ) + { + sal_Int32 nNewLength = m_aElementsSeq.size() + 1; + + if ( nNewLength != 1 ) + throw css::xml::sax::SAXException(); // TODO: this element must be the first level element + + m_aElementsSeq.push_back( aName ); + + return; // nothing to do + } + else if ( aName == g_aRelElement ) + { + sal_Int32 nNewLength = m_aElementsSeq.size() + 1; + if ( nNewLength != 2 ) + throw css::xml::sax::SAXException(); // TODO: this element must be the second level element + + m_aElementsSeq.push_back( aName ); + + sal_Int32 nNewEntryNum = m_aResultSeq.getLength() + 1; + m_aResultSeq.realloc( nNewEntryNum ); + sal_Int32 nAttrNum = 0; + m_aResultSeq[nNewEntryNum-1].realloc( 4 ); // the maximal expected number of arguments is 4 + + OUString aIDValue = xAttribs->getValueByName( g_aIDAttr ); + if ( aIDValue.isEmpty() ) + throw css::xml::sax::SAXException(); // TODO: the ID value must present + + OUString aTypeValue = xAttribs->getValueByName( g_aTypeAttr ); + OUString aTargetValue = xAttribs->getValueByName( g_aTargetAttr ); + OUString aTargetModeValue = xAttribs->getValueByName( g_aTargetModeAttr ); + + m_aResultSeq[nNewEntryNum-1][++nAttrNum - 1].First = g_aIDAttr; + m_aResultSeq[nNewEntryNum-1][nAttrNum - 1].Second = aIDValue; + + if ( !aTypeValue.isEmpty() ) + { + m_aResultSeq[nNewEntryNum-1][++nAttrNum - 1].First = g_aTypeAttr; + m_aResultSeq[nNewEntryNum-1][nAttrNum - 1].Second = aTypeValue; + } + + if ( !aTargetValue.isEmpty() ) + { + m_aResultSeq[nNewEntryNum-1][++nAttrNum - 1].First = g_aTargetAttr; + m_aResultSeq[nNewEntryNum-1][nAttrNum - 1].Second = aTargetValue; + } + + if ( !aTargetModeValue.isEmpty() ) + { + m_aResultSeq[nNewEntryNum-1][++nAttrNum - 1].First = g_aTargetModeAttr; + m_aResultSeq[nNewEntryNum-1][nAttrNum - 1].Second = aTargetModeValue; + } + + m_aResultSeq[nNewEntryNum-1].realloc( nAttrNum ); + } + else + throw css::xml::sax::SAXException(); // TODO: no other elements expected! + } + else if ( m_nFormat == CONTENTTYPE_FORMAT ) + { + if ( aName == g_aTypesElement ) + { + sal_Int32 nNewLength = m_aElementsSeq.size() + 1; + + if ( nNewLength != 1 ) + throw css::xml::sax::SAXException(); // TODO: this element must be the first level element + + m_aElementsSeq.push_back( aName ); + + if ( !m_aResultSeq.hasElements() ) + m_aResultSeq.realloc( 2 ); + + return; // nothing to do + } + else if ( aName == g_aDefaultElement ) + { + sal_Int32 nNewLength = m_aElementsSeq.size() + 1; + if ( nNewLength != 2 ) + throw css::xml::sax::SAXException(); // TODO: this element must be the second level element + + m_aElementsSeq.push_back( aName ); + + if ( !m_aResultSeq.hasElements() ) + m_aResultSeq.realloc( 2 ); + + if ( m_aResultSeq.getLength() != 2 ) + throw uno::RuntimeException(); + + const OUString aExtensionValue = xAttribs->getValueByName( g_aExtensionAttr ); + if ( aExtensionValue.isEmpty() ) + throw css::xml::sax::SAXException(); // TODO: the Extension value must present + + const OUString aContentTypeValue = xAttribs->getValueByName( g_aContentTypeAttr ); + if ( aContentTypeValue.isEmpty() ) + throw css::xml::sax::SAXException(); // TODO: the ContentType value must present + + const sal_Int32 nNewResultLen = m_aResultSeq[0].getLength() + 1; + m_aResultSeq[0].realloc( nNewResultLen ); + + m_aResultSeq[0][nNewResultLen-1].First = aExtensionValue; + m_aResultSeq[0][nNewResultLen-1].Second = aContentTypeValue; + } + else if ( aName == g_aOverrideElement ) + { + sal_Int32 nNewLength = m_aElementsSeq.size() + 1; + if ( nNewLength != 2 ) + throw css::xml::sax::SAXException(); // TODO: this element must be the second level element + + m_aElementsSeq.push_back( aName ); + + if ( !m_aResultSeq.hasElements() ) + m_aResultSeq.realloc( 2 ); + + if ( m_aResultSeq.getLength() != 2 ) + throw uno::RuntimeException(); + + OUString aPartNameValue = xAttribs->getValueByName( g_aPartNameAttr ); + if ( aPartNameValue.isEmpty() ) + throw css::xml::sax::SAXException(); // TODO: the PartName value must present + + OUString aContentTypeValue = xAttribs->getValueByName( g_aContentTypeAttr ); + if ( aContentTypeValue.isEmpty() ) + throw css::xml::sax::SAXException(); // TODO: the ContentType value must present + + sal_Int32 nNewResultLen = m_aResultSeq[1].getLength() + 1; + m_aResultSeq[1].realloc( nNewResultLen ); + + m_aResultSeq[1][nNewResultLen-1].First = aPartNameValue; + m_aResultSeq[1][nNewResultLen-1].Second = aContentTypeValue; + } + else + throw css::xml::sax::SAXException(); // TODO: no other elements expected! + } + else + throw css::xml::sax::SAXException(); // TODO: no other elements expected! +} + + +void SAL_CALL OFOPXMLHelper_Impl::endElement( const OUString& aName ) +{ + if ( m_nFormat == RELATIONINFO_FORMAT || m_nFormat == CONTENTTYPE_FORMAT ) + { + sal_Int32 nLength = m_aElementsSeq.size(); + if ( nLength <= 0 ) + throw css::xml::sax::SAXException(); // TODO: no other end elements expected! + + if ( m_aElementsSeq[nLength-1] != aName ) + throw css::xml::sax::SAXException(); // TODO: unexpected element ended + + m_aElementsSeq.resize( nLength - 1 ); + } +} + + +void SAL_CALL OFOPXMLHelper_Impl::characters( const OUString& /*aChars*/ ) +{ +} + + +void SAL_CALL OFOPXMLHelper_Impl::ignorableWhitespace( const OUString& /*aWhitespaces*/ ) +{ +} + + +void SAL_CALL OFOPXMLHelper_Impl::processingInstruction( const OUString& /*aTarget*/, const OUString& /*aData*/ ) +{ +} + + +void SAL_CALL OFOPXMLHelper_Impl::setDocumentLocator( const uno::Reference< css::xml::sax::XLocator >& /*xLocator*/ ) +{ +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |