summaryrefslogtreecommitdiffstats
path: root/oox/source/core/xmlfilterbase.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'oox/source/core/xmlfilterbase.cxx')
-rw-r--r--oox/source/core/xmlfilterbase.cxx1235
1 files changed, 1235 insertions, 0 deletions
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: */