xref: /core/oox/source/core/xmlfilterbase.cxx (revision 99bc040b)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <oox/core/xmlfilterbase.hxx>
21 
22 #include <cstdio>
23 #include <string_view>
24 
25 #include <com/sun/star/beans/XPropertyAccess.hpp>
26 #include <com/sun/star/beans/XPropertySet.hpp>
27 #include <com/sun/star/beans/Pair.hpp>
28 #include <com/sun/star/embed/XRelationshipAccess.hpp>
29 #include <com/sun/star/frame/XModel.hpp>
30 #include <com/sun/star/xml/sax/XFastSAXSerializable.hpp>
31 #include <com/sun/star/xml/sax/XSAXSerializable.hpp>
32 #include <com/sun/star/xml/sax/Writer.hpp>
33 #include <o3tl/any.hxx>
34 #include <unotools/mediadescriptor.hxx>
35 #include <unotools/docinfohelper.hxx>
36 #include <sax/fshelper.hxx>
37 #include <rtl/strbuf.hxx>
38 #include <rtl/ustrbuf.hxx>
39 #include <rtl/instance.hxx>
40 #include <osl/diagnose.h>
41 #include <sal/log.hxx>
42 #include <i18nlangtag/languagetag.hxx>
43 #include <oox/core/fastparser.hxx>
44 #include <oox/core/fragmenthandler.hxx>
45 #include <oox/core/recordparser.hxx>
46 #include <oox/core/relationshandler.hxx>
47 #include <oox/helper/propertyset.hxx>
48 #include <oox/helper/zipstorage.hxx>
49 #include <oox/ole/olestorage.hxx>
50 #include <oox/token/namespaces.hxx>
51 #include <oox/token/relationship.hxx>
52 #include <oox/token/properties.hxx>
53 #include <oox/token/tokens.hxx>
54 #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
55 #include <com/sun/star/document/XOOXMLDocumentPropertiesImporter.hpp>
56 #include <com/sun/star/xml/dom/DocumentBuilder.hpp>
57 #include <comphelper/processfactory.hxx>
58 #include <oox/core/filterdetect.hxx>
59 #include <comphelper/storagehelper.hxx>
60 #include <comphelper/sequence.hxx>
61 #include <comphelper/ofopxmlhelper.hxx>
62 
63 #include <oox/crypto/DocumentEncryption.hxx>
64 #include <tools/urlobj.hxx>
65 #include <com/sun/star/util/Date.hpp>
66 #include <com/sun/star/util/Duration.hpp>
67 #include <sax/tools/converter.hxx>
68 #include <oox/token/namespacemap.hxx>
69 #include <editeng/unoprnms.hxx>
70 #include <o3tl/sorted_vector.hxx>
71 
72 using ::com::sun::star::xml::dom::DocumentBuilder;
73 using ::com::sun::star::xml::dom::XDocument;
74 using ::com::sun::star::xml::dom::XDocumentBuilder;
75 
76 namespace oox::core {
77 
78 using namespace ::com::sun::star;
79 using namespace ::com::sun::star::beans;
80 using namespace ::com::sun::star::container;
81 using namespace ::com::sun::star::document;
82 using namespace ::com::sun::star::embed;
83 using namespace ::com::sun::star::io;
84 using namespace ::com::sun::star::lang;
85 using namespace ::com::sun::star::uno;
86 using namespace ::com::sun::star::xml::sax;
87 
88 using utl::MediaDescriptor;
89 using ::sax_fastparser::FSHelperPtr;
90 using ::sax_fastparser::FastSerializerHelper;
91 
92 namespace {
93 
94 struct NamespaceIds: public rtl::StaticWithInit<
95     Sequence< beans::Pair< OUString, sal_Int32 > >,
96     NamespaceIds>
97 {
98     Sequence< beans::Pair< OUString, sal_Int32 > > operator()()
99     {
100         return css::uno::Sequence<css::beans::Pair<OUString, sal_Int32>>{
101             {"http://www.w3.org/XML/1998/namespace", NMSP_xml},
102             {"http://schemas.openxmlformats.org/package/2006/relationships",
103              NMSP_packageRel},
104             {"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
105              NMSP_officeRel},
106             {"http://purl.oclc.org/ooxml/officeDocument/relationships",
107              NMSP_officeRel},
108             {"http://schemas.openxmlformats.org/drawingml/2006/main", NMSP_dml},
109             {"http://purl.oclc.org/ooxml/drawingml/main", NMSP_dml},
110             {"http://schemas.openxmlformats.org/drawingml/2006/diagram",
111              NMSP_dmlDiagram},
112             {"http://purl.oclc.org/ooxml/drawingml/diagram", NMSP_dmlDiagram},
113             {"http://schemas.openxmlformats.org/drawingml/2006/chart",
114              NMSP_dmlChart},
115             {"http://schemas.openxmlformats.org/drawingml/2006/chartDrawing",
116              NMSP_dmlChartDr},
117             {"urn:schemas-microsoft-com:vml", NMSP_vml},
118             {"urn:schemas-microsoft-com:office:office", NMSP_vmlOffice},
119             {"urn:schemas-microsoft-com:office:word", NMSP_vmlWord},
120             {"urn:schemas-microsoft-com:office:excel", NMSP_vmlExcel},
121             {"urn:schemas-microsoft-com:office:powerpoint", NMSP_vmlPowerpoint},
122             {"http://schemas.microsoft.com/office/2006/activeX", NMSP_ax},
123             {"http://schemas.openxmlformats.org/spreadsheetml/2006/main",
124              NMSP_xls},
125             {"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing",
126              NMSP_xm},
127             {"http://schemas.microsoft.com/office/excel/2006/main",
128              NMSP_dmlSpreadDr},
129             {"http://schemas.openxmlformats.org/presentationml/2006/main",
130              NMSP_ppt},
131             {"http://schemas.openxmlformats.org/markup-compatibility/2006",
132              NMSP_mce},
133             {"http://schemas.openxmlformats.org/spreadsheetml/2006/main/v2",
134              NMSP_mceTest},
135             {"http://schemas.openxmlformats.org/officeDocument/2006/math",
136              NMSP_officeMath},
137             {"http://schemas.microsoft.com/office/drawing/2008/diagram",
138              NMSP_dsp},
139             {"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main",
140              NMSP_xls14Lst},
141             {"http://schemas.libreoffice.org/", NMSP_loext},
142             {"http://schemas.microsoft.com/office/drawing/2010/main",
143              NMSP_a14},
144             {"http://schemas.microsoft.com/office/powerpoint/2010/main",
145              NMSP_p14},
146             {"http://schemas.microsoft.com/office/powerpoint/2012/main",
147              NMSP_p15},
148             {"http://schemas.microsoft.com/office/spreadsheetml/2011/1/ac",
149              NMSP_x12ac},
150             {"http://schemas.microsoft.com/office/drawing/2012/chart",
151              NMSP_c15},
152         };
153     }
154 };
155 
156 void registerNamespaces( FastParser& rParser )
157 {
158     const Sequence< beans::Pair<OUString, sal_Int32> > ids = NamespaceIds::get();
159 
160     // Filter out duplicates: a namespace can have multiple URLs, think of
161     // strict vs transitional.
162     o3tl::sorted_vector<sal_Int32> aSet;
163     aSet.reserve(ids.getLength());
164     for (const auto& rId : ids)
165         aSet.insert(rId.Second);
166 
167     for (auto const& elem : aSet)
168         rParser.registerNamespace(elem);
169 }
170 
171 } // namespace
172 
173 struct XmlFilterBaseImpl
174 {
175     typedef RefMap< OUString, Relations > RelationsMap;
176 
177     FastParser                     maFastParser;
178     RelationsMap                   maRelationsMap;
179     TextFieldStack                 maTextFieldStack;
180     const NamespaceMap&            mrNamespaceMap;
181     NamedShapePairs* mpDiagramFontHeights = nullptr;
182 
183     /// @throws RuntimeException
184     explicit            XmlFilterBaseImpl();
185 };
186 
187 constexpr OUStringLiteral gaBinSuffix( u".bin" );
188 
189 XmlFilterBaseImpl::XmlFilterBaseImpl() :
190     mrNamespaceMap(StaticNamespaceMap::get())
191 {
192     // register XML namespaces
193     registerNamespaces(maFastParser);
194 }
195 
196 XmlFilterBase::XmlFilterBase( const Reference< XComponentContext >& rxContext ) :
197     FilterBase( rxContext ),
198     mxImpl( new XmlFilterBaseImpl ),
199     mnRelId( 1 ),
200     mnMaxDocId( 0 ),
201     mbMSO2007(false),
202     mbMissingExtDrawing(false)
203 {
204 }
205 
206 XmlFilterBase::~XmlFilterBase()
207 {
208     // #i118640# Reset the DocumentHandler at the FastSaxParser manually; this is
209     // needed since the mechanism is that instances of FragmentHandler execute
210     // their stuff (creating objects, setting attributes, ...) on being destroyed.
211     // They get destroyed by setting a new DocumentHandler. This also happens in
212     // the following implicit destruction chain of ~XmlFilterBaseImpl, but in that
213     // case it's member RelationsMap maRelationsMap will be destroyed, but maybe
214     // still be used by ~FragmentHandler -> crash.
215     mxImpl->maFastParser.clearDocumentHandler();
216 }
217 
218 void XmlFilterBase::checkDocumentProperties(const Reference<XDocumentProperties>& xDocProps)
219 {
220     mbMSO2007 = false;
221     if (!xDocProps->getGenerator().startsWithIgnoreAsciiCase("Microsoft"))
222         return;
223 
224     uno::Reference<beans::XPropertyAccess> xUserDefProps(xDocProps->getUserDefinedProperties(), uno::UNO_QUERY);
225     if (!xUserDefProps.is())
226         return;
227 
228     comphelper::SequenceAsHashMap aUserDefinedProperties(xUserDefProps->getPropertyValues());
229     comphelper::SequenceAsHashMap::iterator it = aUserDefinedProperties.find("AppVersion");
230     if (it == aUserDefinedProperties.end())
231         return;
232 
233     OUString aValue;
234     if (!(it->second >>= aValue))
235         return;
236 
237     if (!aValue.startsWithIgnoreAsciiCase("12."))
238         return;
239 
240     SAL_INFO("oox", "a MSO 2007 document");
241     mbMSO2007 = true;
242 }
243 
244 void XmlFilterBase::putPropertiesToDocumentGrabBag(const css::uno::Reference<css::lang::XComponent>& xDstDoc,
245                                                    const comphelper::SequenceAsHashMap& rProperties)
246 {
247     try
248     {
249         uno::Reference<beans::XPropertySet> xDocProps(xDstDoc, uno::UNO_QUERY);
250         if (xDocProps.is())
251         {
252             uno::Reference<beans::XPropertySetInfo> xPropsInfo = xDocProps->getPropertySetInfo();
253 
254             static constexpr OUStringLiteral aGrabBagPropName = u"InteropGrabBag";
255             if (xPropsInfo.is() && xPropsInfo->hasPropertyByName(aGrabBagPropName))
256             {
257                 // get existing grab bag
258                 comphelper::SequenceAsHashMap aGrabBag(xDocProps->getPropertyValue(aGrabBagPropName));
259 
260                 // put the new items
261                 aGrabBag.update(rProperties);
262 
263                 // put it back to the document
264                 xDocProps->setPropertyValue(aGrabBagPropName, uno::Any(aGrabBag.getAsConstPropertyValueList()));
265             }
266         }
267     }
268     catch (const uno::Exception&)
269     {
270         SAL_WARN("oox","Failed to save documents grab bag");
271     }
272 }
273 
274 void XmlFilterBase::importDocumentProperties()
275 {
276     MediaDescriptor aMediaDesc( getMediaDescriptor() );
277     Reference< XInputStream > xInputStream;
278     Reference< XComponentContext > xContext = getComponentContext();
279     rtl::Reference< ::oox::core::FilterDetect > xDetector( new ::oox::core::FilterDetect( xContext ) );
280     xInputStream = xDetector->extractUnencryptedPackage( aMediaDesc );
281     Reference< XComponent > xModel = getModel();
282     Reference< XStorage > xDocumentStorage (
283             ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream( OFOPXML_STORAGE_FORMAT_STRING, xInputStream ) );
284     Reference< XInterface > xTemp = xContext->getServiceManager()->createInstanceWithContext(
285             "com.sun.star.document.OOXMLDocumentPropertiesImporter",
286             xContext);
287     Reference< XOOXMLDocumentPropertiesImporter > xImporter( xTemp, UNO_QUERY );
288     Reference< XDocumentPropertiesSupplier > xPropSupplier( xModel, UNO_QUERY);
289     Reference< XDocumentProperties > xDocProps = xPropSupplier->getDocumentProperties();
290     xImporter->importProperties( xDocumentStorage, xDocProps );
291     checkDocumentProperties(xDocProps);
292 
293     importCustomFragments(xDocumentStorage);
294 }
295 
296 FastParser* XmlFilterBase::createParser()
297 {
298     FastParser* pParser = new FastParser;
299     registerNamespaces(*pParser);
300     return pParser;
301 }
302 
303 namespace {
304 
305 OUString getTransitionalRelationshipOfficeDocType(std::u16string_view rPart)
306 {
307     return OUString::Concat("http://schemas.openxmlformats.org/officeDocument/2006/relationships/")
308         + rPart;
309 }
310 
311 OUString getStrictRelationshipOfficeDocType(std::u16string_view rPart)
312 {
313     return OUString::Concat("http://purl.oclc.org/ooxml/officeDocument/relationships/") + rPart;
314 }
315 
316 }
317 
318 OUString XmlFilterBase::getFragmentPathFromFirstTypeFromOfficeDoc( std::u16string_view rPart )
319 {
320     // importRelations() caches the relations map for subsequence calls
321     const OUString aTransitionalRelationshipType = getTransitionalRelationshipOfficeDocType(rPart);
322     OUString aFragment = importRelations( OUString() )->getFragmentPathFromFirstType( aTransitionalRelationshipType );
323     if(aFragment.isEmpty())
324     {
325         const OUString aStrictRelationshipType = getStrictRelationshipOfficeDocType(rPart);
326         aFragment = importRelations( OUString() )->getFragmentPathFromFirstType( aStrictRelationshipType );
327     }
328 
329     return aFragment;
330 }
331 
332 bool XmlFilterBase::importFragment( const rtl::Reference<FragmentHandler>& rxHandler )
333 {
334     FastParser aParser;
335     registerNamespaces(aParser);
336     return importFragment(rxHandler, aParser);
337 }
338 
339 bool XmlFilterBase::importFragment( const rtl::Reference<FragmentHandler>& rxHandler, FastParser& rParser )
340 {
341     OSL_ENSURE( rxHandler.is(), "XmlFilterBase::importFragment - missing fragment handler" );
342     if( !rxHandler.is() )
343         return false;
344 
345     // fragment handler must contain path to fragment stream
346     OUString aFragmentPath = rxHandler->getFragmentPath();
347     OSL_ENSURE( !aFragmentPath.isEmpty(), "XmlFilterBase::importFragment - missing fragment path" );
348     if( aFragmentPath.isEmpty() )
349         return false;
350 
351     // try to import binary streams (fragment extension must be '.bin')
352     if (aFragmentPath.endsWith(gaBinSuffix))
353     {
354         try
355         {
356             // try to open the fragment stream (this may fail - do not assert)
357             Reference< XInputStream > xInStrm( openInputStream( aFragmentPath ), UNO_SET_THROW );
358 
359             // create the record parser
360             RecordParser aParser;
361             aParser.setFragmentHandler( rxHandler );
362 
363             // create the input source and parse the stream
364             RecordInputSource aSource;
365             aSource.mxInStream = std::make_shared<BinaryXInputStream>( xInStrm, true );
366             aSource.maSystemId = aFragmentPath;
367             aParser.parseStream( aSource );
368             return true;
369         }
370         catch( Exception& )
371         {
372         }
373         return false;
374     }
375 
376     // get the XFastDocumentHandler interface from the fragment handler
377     if( !rxHandler.is() )
378         return false;
379 
380     // try to import XML stream
381     try
382     {
383         /*  Try to open the fragment stream (may fail, do not throw/assert).
384             Using the virtual function openFragmentStream() allows a document
385             handler to create specialized input streams, e.g. VML streams that
386             have to preprocess the raw input data. */
387         Reference< XInputStream > xInStrm = rxHandler->openFragmentStream();
388         /*  tdf#100084 Check again the aFragmentPath route with lowercase file name
389             TODO: complete handling of case-insensitive file paths */
390         if ( !xInStrm.is() )
391         {
392             sal_Int32 nPathLen = aFragmentPath.lastIndexOf('/') + 1;
393             OUString fileName = aFragmentPath.copy(nPathLen);
394             OUString sLowerCaseFileName = fileName.toAsciiLowerCase();
395             if ( fileName != sLowerCaseFileName )
396             {
397                 aFragmentPath = aFragmentPath.subView(0, nPathLen) + sLowerCaseFileName;
398                 xInStrm = openInputStream(aFragmentPath);
399             }
400         }
401 
402         // own try/catch block for showing parser failure assertion with fragment path
403         if( xInStrm.is() ) try
404         {
405             rParser.setDocumentHandler(rxHandler);
406             rParser.parseStream(xInStrm, aFragmentPath);
407             return true;
408         }
409         catch( Exception& )
410         {
411             OSL_FAIL( OStringBuffer( "XmlFilterBase::importFragment - XML parser failed in fragment '" ).
412                 append( OUStringToOString( aFragmentPath, RTL_TEXTENCODING_ASCII_US ) ).append( '\'' ).getStr() );
413         }
414     }
415     catch( Exception& )
416     {
417     }
418     return false;
419 }
420 
421 Reference<XDocument> XmlFilterBase::importFragment( const OUString& aFragmentPath )
422 {
423     Reference<XDocument> xRet;
424 
425     // path to fragment stream valid?
426     OSL_ENSURE( !aFragmentPath.isEmpty(), "XmlFilterBase::importFragment - empty fragment path" );
427     if( aFragmentPath.isEmpty() )
428         return xRet;
429 
430     // try to open the fragment stream (this may fail - do not assert)
431     Reference< XInputStream > xInStrm = openInputStream( aFragmentPath );
432     if( !xInStrm.is() )
433         return xRet;
434 
435     // binary streams (fragment extension is '.bin') currently not supported
436     if (aFragmentPath.endsWith(gaBinSuffix))
437         return xRet;
438 
439     // try to import XML stream
440     try
441     {
442         // create the dom parser
443         Reference<XDocumentBuilder> xDomBuilder( DocumentBuilder::create( getComponentContext() ) );
444 
445         // create DOM from fragment
446         xRet = xDomBuilder->parse(xInStrm);
447     }
448     catch( Exception& )
449     {
450     }
451 
452     return xRet;
453 }
454 
455 bool XmlFilterBase::importFragment( const ::rtl::Reference< FragmentHandler >& rxHandler,
456                                     const Reference< XFastSAXSerializable >& rxSerializer )
457 {
458     if( !rxHandler.is() )
459         return false;
460 
461     // try to import XML stream
462     try
463     {
464         rxSerializer->fastSerialize( rxHandler,
465                                      mxImpl->maFastParser.getTokenHandler(),
466                                      Sequence< StringPair >(),
467                                      NamespaceIds::get() );
468         return true;
469     }
470     catch( Exception& )
471     {}
472 
473     return false;
474 }
475 
476 RelationsRef XmlFilterBase::importRelations( const OUString& rFragmentPath )
477 {
478     // try to find cached relations
479     RelationsRef& rxRelations = mxImpl->maRelationsMap[ rFragmentPath ];
480     if( !rxRelations )
481     {
482         // import and cache relations
483         rxRelations = std::make_shared<Relations>( rFragmentPath );
484         importFragment( new RelationsFragment( *this, rxRelations ) );
485     }
486     return rxRelations;
487 }
488 
489 Reference< XOutputStream > XmlFilterBase::openFragmentStream( const OUString& rStreamName, const OUString& rMediaType )
490 {
491     Reference< XOutputStream > xOutputStream = openOutputStream( rStreamName );
492     PropertySet aPropSet( xOutputStream );
493     aPropSet.setProperty( PROP_MediaType, rMediaType );
494     return xOutputStream;
495 }
496 
497 FSHelperPtr XmlFilterBase::openFragmentStreamWithSerializer( const OUString& rStreamName, const OUString& rMediaType )
498 {
499     const bool bWriteHeader = rMediaType.indexOf( "vml" ) < 0 || rMediaType.indexOf( "+xml" ) >= 0;
500     return std::make_shared<FastSerializerHelper>( openFragmentStream( rStreamName, rMediaType ), bWriteHeader );
501 }
502 
503 TextFieldStack& XmlFilterBase::getTextFieldStack() const
504 {
505     return mxImpl->maTextFieldStack;
506 }
507 
508 namespace {
509 
510 OUString lclAddRelation( const Reference< XRelationshipAccess >& rRelations, sal_Int32 nId, const OUString& rType, const OUString& rTarget, bool bExternal )
511 {
512     OUString sId = "rId" + OUString::number( nId );
513 
514     Sequence< StringPair > aEntry( bExternal ? 3 : 2 );
515     aEntry[0].First = "Type";
516     aEntry[0].Second = rType;
517     aEntry[1].First = "Target";
518     aEntry[1].Second = INetURLObject::decode(rTarget, INetURLObject::DecodeMechanism::ToIUri, RTL_TEXTENCODING_UTF8);
519     if( bExternal )
520     {
521         aEntry[2].First = "TargetMode";
522         aEntry[2].Second = "External";
523     }
524     rRelations->insertRelationshipByID( sId, aEntry, true );
525 
526     return sId;
527 }
528 
529 } // namespace
530 
531 OUString XmlFilterBase::addRelation( const OUString& rType, const OUString& rTarget )
532 {
533     Reference< XRelationshipAccess > xRelations( getStorage()->getXStorage(), UNO_QUERY );
534     if( xRelations.is() )
535         return lclAddRelation( xRelations, mnRelId ++, rType, rTarget, false/*bExternal*/ );
536 
537     return OUString();
538 }
539 
540 OUString XmlFilterBase::addRelation( const Reference< XOutputStream >& rOutputStream, const OUString& rType, const OUString& rTarget, bool bExternal )
541 {
542     sal_Int32 nId = 0;
543 
544     PropertySet aPropSet( rOutputStream );
545     if( aPropSet.is() )
546         aPropSet.getProperty( nId, PROP_RelId );
547     else
548         nId = mnRelId++;
549 
550     Reference< XRelationshipAccess > xRelations( rOutputStream, UNO_QUERY );
551     if( xRelations.is() )
552         return lclAddRelation( xRelations, nId, rType, rTarget, bExternal );
553 
554     return OUString();
555 }
556 
557 static void
558 writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, std::u16string_view sValue )
559 {
560     pDoc->startElement(nXmlElement);
561     pDoc->writeEscaped( sValue );
562     pDoc->endElement( nXmlElement );
563 }
564 
565 static void
566 writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, const sal_Int32 nValue )
567 {
568     pDoc->startElement(nXmlElement);
569     pDoc->write( nValue );
570     pDoc->endElement( nXmlElement );
571 }
572 
573 static void
574 writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, const util::DateTime& rTime )
575 {
576     if( rTime.Year == 0 )
577         return;
578 
579     if ( ( nXmlElement >> 16 ) != XML_dcterms )
580         pDoc->startElement(nXmlElement);
581     else
582         pDoc->startElement(nXmlElement, FSNS(XML_xsi, XML_type), "dcterms:W3CDTF");
583 
584     char pStr[200];
585     snprintf( pStr, sizeof( pStr ), "%d-%02d-%02dT%02d:%02d:%02dZ",
586             rTime.Year, rTime.Month, rTime.Day,
587             rTime.Hours, rTime.Minutes, rTime.Seconds );
588 
589     pDoc->write( pStr );
590 
591     pDoc->endElement( nXmlElement );
592 }
593 
594 static void
595 writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, const Sequence< OUString >& aItems )
596 {
597     if( !aItems.hasElements() )
598         return;
599 
600     OUStringBuffer sRep;
601     sRep.append( aItems[ 0 ] );
602 
603     for( const OUString& rItem : aItems )
604     {
605         sRep.append( " " ).append( rItem );
606     }
607 
608     writeElement( pDoc, nXmlElement, sRep.makeStringAndClear() );
609 }
610 
611 static void
612 writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, const LanguageTag& rLanguageTag )
613 {
614     // dc:language, Dublin Core recommends "such as RFC 4646", which is BCP 47
615     // and obsoleted by RFC 5646, see
616     // http://dublincore.org/documents/dcmi-terms/#terms-language
617     // http://dublincore.org/documents/dcmi-terms/#elements-language
618     writeElement( pDoc, nXmlElement, rLanguageTag.getBcp47MS() );
619 }
620 
621 static void
622 writeCoreProperties( XmlFilterBase& rSelf, const Reference< XDocumentProperties >& xProperties )
623 {
624     OUString sValue;
625     if( rSelf.getVersion() == oox::core::ISOIEC_29500_2008  )
626     {
627         // The lowercase "officedocument" is intentional and according to the spec
628         // (although most other places are written "officeDocument")
629         sValue = "http://schemas.openxmlformats.org/officedocument/2006/relationships/metadata/core-properties";
630     }
631     else
632         sValue = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
633 
634     rSelf.addRelation( sValue, "docProps/core.xml" );
635     FSHelperPtr pCoreProps = rSelf.openFragmentStreamWithSerializer(
636             "docProps/core.xml",
637             "application/vnd.openxmlformats-package.core-properties+xml" );
638     pCoreProps->startElementNS( XML_cp, XML_coreProperties,
639         FSNS(XML_xmlns, XML_cp),       rSelf.getNamespaceURL(OOX_NS(packageMetaCorePr)),
640         FSNS(XML_xmlns, XML_dc),       rSelf.getNamespaceURL(OOX_NS(dc)),
641         FSNS(XML_xmlns, XML_dcterms),  rSelf.getNamespaceURL(OOX_NS(dcTerms)),
642         FSNS(XML_xmlns, XML_dcmitype), rSelf.getNamespaceURL(OOX_NS(dcmiType)),
643         FSNS(XML_xmlns, XML_xsi),      rSelf.getNamespaceURL(OOX_NS(xsi)));
644 
645     uno::Reference<beans::XPropertyAccess> xUserDefinedProperties(xProperties->getUserDefinedProperties(), uno::UNO_QUERY);
646     comphelper::SequenceAsHashMap aUserDefinedProperties(xUserDefinedProperties->getPropertyValues());
647     comphelper::SequenceAsHashMap::iterator it;
648 
649     it = aUserDefinedProperties.find("OOXMLCorePropertyCategory");
650     if (it != aUserDefinedProperties.end())
651     {
652         OUString aValue;
653         if (it->second >>= aValue)
654             writeElement( pCoreProps, FSNS( XML_cp, XML_category ), aValue );
655     }
656 
657     it = aUserDefinedProperties.find("OOXMLCorePropertyContentStatus");
658     if (it != aUserDefinedProperties.end())
659     {
660         OUString aValue;
661         if (it->second >>= aValue)
662             writeElement( pCoreProps, FSNS( XML_cp, XML_contentStatus ), aValue );
663     }
664 
665     it = aUserDefinedProperties.find("OOXMLCorePropertyContentType");
666     if (it != aUserDefinedProperties.end())
667     {
668         OUString aValue;
669         if (it->second >>= aValue)
670             writeElement( pCoreProps, FSNS( XML_cp, XML_contentType ), aValue );
671     }
672     writeElement( pCoreProps, FSNS( XML_dcterms, XML_created ),     xProperties->getCreationDate() );
673     writeElement( pCoreProps, FSNS( XML_dc, XML_creator ),          xProperties->getAuthor() );
674     writeElement( pCoreProps, FSNS( XML_dc, XML_description ),      xProperties->getDescription() );
675 
676     it = aUserDefinedProperties.find("OOXMLCorePropertyIdentifier");
677     if (it != aUserDefinedProperties.end())
678     {
679         OUString aValue;
680         if (it->second >>= aValue)
681             writeElement( pCoreProps, FSNS( XML_dc, XML_identifier ), aValue );
682     }
683     writeElement( pCoreProps, FSNS( XML_cp, XML_keywords ),         xProperties->getKeywords() );
684     writeElement( pCoreProps, FSNS( XML_dc, XML_language ),         LanguageTag( xProperties->getLanguage()) );
685     writeElement( pCoreProps, FSNS( XML_cp, XML_lastModifiedBy ),   xProperties->getModifiedBy() );
686     writeElement( pCoreProps, FSNS( XML_cp, XML_lastPrinted ),      xProperties->getPrintDate() );
687     writeElement( pCoreProps, FSNS( XML_dcterms, XML_modified ),    xProperties->getModificationDate() );
688     writeElement( pCoreProps, FSNS( XML_cp, XML_revision ),         xProperties->getEditingCycles() );
689     writeElement( pCoreProps, FSNS( XML_dc, XML_subject ),          xProperties->getSubject() );
690     writeElement( pCoreProps, FSNS( XML_dc, XML_title ),            xProperties->getTitle() );
691 
692     it = aUserDefinedProperties.find("OOXMLCorePropertyVersion");
693     if (it != aUserDefinedProperties.end())
694     {
695         OUString aValue;
696         if (it->second >>= aValue)
697             writeElement( pCoreProps, FSNS( XML_cp, XML_version ), aValue );
698     }
699 
700     pCoreProps->endElementNS( XML_cp, XML_coreProperties );
701 }
702 
703 static void
704 writeAppProperties( XmlFilterBase& rSelf, const Reference< XDocumentProperties >& xProperties )
705 {
706     rSelf.addRelation(
707             "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",
708             "docProps/app.xml" );
709     FSHelperPtr pAppProps = rSelf.openFragmentStreamWithSerializer(
710             "docProps/app.xml",
711             "application/vnd.openxmlformats-officedocument.extended-properties+xml" );
712     pAppProps->startElement( XML_Properties,
713             XML_xmlns,               rSelf.getNamespaceURL(OOX_NS(officeExtPr)),
714             FSNS(XML_xmlns, XML_vt), rSelf.getNamespaceURL(OOX_NS(officeDocPropsVT)));
715 
716     uno::Reference<beans::XPropertyAccess> xUserDefinedProperties(xProperties->getUserDefinedProperties(), uno::UNO_QUERY);
717     comphelper::SequenceAsHashMap aUserDefinedProperties(xUserDefinedProperties->getPropertyValues());
718     comphelper::SequenceAsHashMap::iterator it;
719 
720     writeElement( pAppProps, XML_Template,              xProperties->getTemplateName() );
721 
722     it = aUserDefinedProperties.find("Manager");
723     if (it != aUserDefinedProperties.end())
724     {
725         OUString aValue;
726         if (it->second >>= aValue)
727             writeElement( pAppProps, XML_Manager,       aValue );
728     }
729 
730 #ifdef OOXTODO
731     writeElement( pAppProps, XML_PresentationFormat,    "presentation format" );
732     writeElement( pAppProps, XML_Lines,                 "lines" );
733     writeElement( pAppProps, XML_Slides,                "slides" );
734     writeElement( pAppProps, XML_Notes,                 "notes" );
735 #endif  /* def OOXTODO */
736     // EditingDuration is in seconds, TotalTime is in minutes.
737     writeElement( pAppProps, XML_TotalTime,             xProperties->getEditingDuration() / 60 );
738 #ifdef OOXTODO
739     writeElement( pAppProps, XML_HiddenSlides,          "hidden slides" );
740     writeElement( pAppProps, XML_MMClips,               "mm clips" );
741     writeElement( pAppProps, XML_ScaleCrop,             "scale crop" );
742     writeElement( pAppProps, XML_HeadingPairs,          "heading pairs" );
743     writeElement( pAppProps, XML_TitlesOfParts,         "titles of parts" );
744     writeElement( pAppProps, XML_LinksUpToDate,         "links up-to-date" );
745     writeElement( pAppProps, XML_SharedDoc,             "shared doc" );
746     writeElement( pAppProps, XML_HLinks,                "hlinks" );
747     writeElement( pAppProps, XML_HyperlinksChanged,     "hyperlinks changed" );
748     writeElement( pAppProps, XML_DigSig,                "digital signature" );
749 #endif  /* def OOXTODO */
750     writeElement( pAppProps, XML_Application,           utl::DocInfoHelper::GetGeneratorString() );
751 
752     it = aUserDefinedProperties.find("HyperlinkBase");
753     if (it != aUserDefinedProperties.end())
754     {
755         OUString aValue;
756         if (it->second >>= aValue)
757             writeElement( pAppProps, XML_HyperlinkBase, aValue );
758     }
759     // AppVersion specifies the version of the application which produced document
760     // It is strictly connected with MS Office versions:
761     //     * 12:  [Office 2007]  [LO < 7.0]
762     //     * 14:  [Office 2010]
763     //     * 15:  [Office 2013/2016/2019]  [LO >= 7.0]
764     // The LibreOffice is application on 2013/2016/2019 level
765     writeElement( pAppProps, XML_AppVersion, u"15.0000" );
766 
767     // OOXTODO Calculate DocSecurity value based on security (password, read-only etc.)
768     it = aUserDefinedProperties.find("DocSecurity");
769     if (it != aUserDefinedProperties.end())
770     {
771         sal_Int32 nValue;
772         if (it->second >>= nValue)
773             writeElement( pAppProps, XML_DocSecurity, nValue );
774     }
775 
776     comphelper::SequenceAsHashMap aStats = xProperties->getDocumentStatistics();
777     sal_Int32 nValue = 0;
778 
779     it = aStats.find("PageCount");
780     if (it != aStats.end())
781     {
782             if (it->second >>= nValue)
783                 writeElement(pAppProps, XML_Pages, nValue);
784     }
785 
786     it = aStats.find("WordCount");
787     if (it != aStats.end())
788     {
789             if (it->second >>= nValue)
790                 writeElement(pAppProps, XML_Words, nValue);
791     }
792 
793     it = aStats.find("NonWhitespaceCharacterCount");
794     if (it != aStats.end())
795     {
796             if (it->second >>= nValue)
797                 writeElement(pAppProps, XML_Characters, nValue);
798     }
799 
800     it = aStats.find("CharacterCount");
801     if (it != aStats.end())
802     {
803             if (it->second >>= nValue)
804                 writeElement(pAppProps, XML_CharactersWithSpaces, nValue);
805     }
806 
807     it = aStats.find("ParagraphCount");
808     if (it != aStats.end())
809     {
810             if (it->second >>= nValue)
811                 writeElement(pAppProps, XML_Paragraphs, nValue);
812     }
813 
814     it = aUserDefinedProperties.find("Company");
815     if (it != aUserDefinedProperties.end())
816     {
817         OUString aValue;
818         if (it->second >>= aValue)
819             writeElement(pAppProps, XML_Company, aValue);
820     }
821 
822     pAppProps->endElement( XML_Properties );
823 }
824 
825 static void
826 writeCustomProperties( XmlFilterBase& rSelf, const Reference< XDocumentProperties >& xProperties, bool bSecurityOptOpenReadOnly )
827 {
828     uno::Reference<beans::XPropertyAccess> xUserDefinedProperties( xProperties->getUserDefinedProperties(), uno::UNO_QUERY );
829     auto aprop = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(xUserDefinedProperties->getPropertyValues());
830     sal_Int32 nbCustomProperties = aprop.size();
831     // tdf#89791 : if no custom properties, no need to add docProps/custom.x
832     // tdf#107690: except the case of read-only documents, because that
833     // is handled by the _MarkAsFinal custom property in MSO.
834     if (!nbCustomProperties && !bSecurityOptOpenReadOnly)
835         return;
836 
837     if (bSecurityOptOpenReadOnly)
838     {
839         PropertyValue aPropertyValue;
840         // MSO custom property for read-only documents
841         aPropertyValue.Name = "_MarkAsFinal";
842         aPropertyValue.Value <<= true;
843         aprop.push_back(aPropertyValue);
844     }
845 
846     rSelf.addRelation(
847             "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties",
848             "docProps/custom.xml" );
849     FSHelperPtr pAppProps = rSelf.openFragmentStreamWithSerializer(
850             "docProps/custom.xml",
851             "application/vnd.openxmlformats-officedocument.custom-properties+xml" );
852     pAppProps->startElement( XML_Properties,
853             XML_xmlns,               rSelf.getNamespaceURL(OOX_NS(officeCustomPr)),
854             FSNS(XML_xmlns, XML_vt), rSelf.getNamespaceURL(OOX_NS(officeDocPropsVT)));
855 
856     size_t nIndex = 0;
857     for (const auto& rProp : aprop)
858     {
859         if ( !rProp.Name.isEmpty() )
860         {
861             OString aName = OUStringToOString( rProp.Name, RTL_TEXTENCODING_ASCII_US );
862             // Skip storing these values in Custom Properties as it will be stored in Core/Extended Properties
863             if (( aName == "OOXMLCorePropertyCategory" ) || // stored in cp:category
864                 ( aName == "OOXMLCorePropertyContentStatus" ) || // stored in cp:contentStatus
865                 ( aName == "OOXMLCorePropertyContentType" ) || // stored in cp:contentType
866                 ( aName == "OOXMLCorePropertyIdentifier" ) || // stored in dc:identifier
867                 ( aName == "OOXMLCorePropertyVersion" ) || // stored in cp:version
868                 ( aName == "HyperlinkBase" ) || // stored in Extended File Properties
869                 ( aName == "AppVersion" ) || // stored in Extended File Properties
870                 ( aName == "DocSecurity" ) || // stored in Extended File Properties
871                 ( aName == "Manager" ) || // stored in Extended File Properties
872                 ( aName == "Company" )) // stored in Extended File Properties
873                 continue;
874 
875             // pid starts from 2 not from 1 as MS supports pid from 2
876             pAppProps->startElement( XML_property ,
877                 XML_fmtid,  "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
878                 XML_pid,    OString::number(nIndex + 2),
879                 XML_name,   aName);
880 
881             switch ( rProp.Value.getValueTypeClass() )
882             {
883                 case TypeClass_STRING:
884                 {
885                     OUString aValue;
886                     rProp.Value >>= aValue;
887                     writeElement( pAppProps, FSNS( XML_vt, XML_lpwstr ), aValue );
888                 }
889                 break;
890                 case TypeClass_BOOLEAN:
891                 {
892                     bool val = *o3tl::forceAccess<bool>(rProp.Value);
893                     writeElement( pAppProps, FSNS( XML_vt, XML_bool ), val ? 1 : 0);
894                 }
895                 break;
896                 case TypeClass_DOUBLE:
897                 {
898                     double num = {}; // spurious -Werror=maybe-uninitialized
899                     if ( rProp.Value >>= num )
900                     {
901                         // r8 - 8-byte real number
902                         writeElement( pAppProps, FSNS( XML_vt, XML_r8 ), OUString::number(num) );
903                     }
904                 }
905                 break;
906                 default:
907                 {
908                     double num = {}; // spurious -Werror=maybe-uninitialized
909                     util::Date aDate;
910                     util::Duration aDuration;
911                     util::DateTime aDateTime;
912                     if ( rProp.Value >>= num )
913                     {
914                         // i4 - 4-byte signed integer
915                         writeElement( pAppProps, FSNS( XML_vt, XML_i4 ), num );
916                     }
917                     else if ( rProp.Value >>= aDate )
918                     {
919                         aDateTime = util::DateTime( 0, 0 , 0, 0, aDate.Day, aDate.Month, aDate.Year, true );
920                         writeElement( pAppProps, FSNS( XML_vt, XML_filetime ), aDateTime);
921                     }
922                     else if ( rProp.Value >>= aDuration )
923                     {
924                         OUStringBuffer buf;
925                         ::sax::Converter::convertDuration( buf, aDuration );
926                         OUString aDurationStr = buf.makeStringAndClear();
927                         writeElement( pAppProps, FSNS( XML_vt, XML_lpwstr ), aDurationStr );
928                     }
929                     else if ( rProp.Value >>= aDateTime )
930                             writeElement( pAppProps, FSNS( XML_vt, XML_filetime ), aDateTime );
931                     else
932                         //no other options
933                         OSL_FAIL( "XMLFilterBase::writeCustomProperties unsupported value type!" );
934                  }
935                  break;
936             }
937             pAppProps->endElement( XML_property );
938         }
939         ++nIndex;
940     }
941     pAppProps->endElement( XML_Properties );
942 }
943 
944 void XmlFilterBase::exportDocumentProperties( const Reference< XDocumentProperties >& xProperties, bool bSecurityOptOpenReadOnly )
945 {
946     if( xProperties.is() )
947     {
948         writeCoreProperties( *this, xProperties );
949         writeAppProperties( *this, xProperties );
950         writeCustomProperties( *this, xProperties, bSecurityOptOpenReadOnly );
951     }
952 }
953 
954 // protected ------------------------------------------------------------------
955 
956 Reference< XInputStream > XmlFilterBase::implGetInputStream( MediaDescriptor& rMediaDesc ) const
957 {
958     /*  Get the input stream directly from the media descriptor, or decrypt the
959         package again. The latter is needed e.g. when the document is reloaded.
960         All this is implemented in the detector service. */
961     rtl::Reference< FilterDetect > xDetector( new FilterDetect( getComponentContext() ) );
962     return xDetector->extractUnencryptedPackage( rMediaDesc );
963 }
964 
965 Reference<XStream> XmlFilterBase::implGetOutputStream( MediaDescriptor& rMediaDescriptor ) const
966 {
967     const Sequence< NamedValue > aMediaEncData = rMediaDescriptor.getUnpackedValueOrDefault(
968                                         MediaDescriptor::PROP_ENCRYPTIONDATA(),
969                                         Sequence< NamedValue >() );
970 
971     if (aMediaEncData.getLength() == 0)
972     {
973         return FilterBase::implGetOutputStream( rMediaDescriptor );
974     }
975     else // We need to encrypt the stream so create a memory stream
976     {
977         Reference< XComponentContext > xContext = getComponentContext();
978         return Reference< XStream > (
979                     xContext->getServiceManager()->createInstanceWithContext("com.sun.star.comp.MemoryStream", xContext),
980                     uno::UNO_QUERY_THROW );
981     }
982 }
983 
984 bool XmlFilterBase::implFinalizeExport( MediaDescriptor& rMediaDescriptor )
985 {
986     bool bRet = true;
987 
988     const Sequence< NamedValue > aMediaEncData = rMediaDescriptor.getUnpackedValueOrDefault(
989                                         MediaDescriptor::PROP_ENCRYPTIONDATA(),
990                                         Sequence< NamedValue >() );
991 
992     if (aMediaEncData.getLength())
993     {
994         commitStorage();
995 
996         Reference< XStream> xDocumentStream (FilterBase::implGetOutputStream(rMediaDescriptor));
997         oox::ole::OleStorage aOleStorage( getComponentContext(), xDocumentStream, true );
998         crypto::DocumentEncryption encryptor( getComponentContext(), getMainDocumentStream(), aOleStorage, aMediaEncData );
999         bRet = encryptor.encrypt();
1000         if (bRet)
1001             aOleStorage.commit();
1002     }
1003 
1004     return bRet;
1005 }
1006 
1007 // private --------------------------------------------------------------------
1008 
1009 StorageRef XmlFilterBase::implCreateStorage( const Reference< XInputStream >& rxInStream ) const
1010 {
1011     return std::make_shared<ZipStorage>( getComponentContext(), rxInStream );
1012 }
1013 
1014 StorageRef XmlFilterBase::implCreateStorage( const Reference< XStream >& rxOutStream ) const
1015 {
1016     return std::make_shared<ZipStorage>( getComponentContext(), rxOutStream );
1017 }
1018 
1019 bool XmlFilterBase::isMSO2007Document() const
1020 {
1021     return mbMSO2007;
1022 }
1023 
1024 void XmlFilterBase::setMissingExtDrawing()
1025 {
1026     mbMissingExtDrawing = true;
1027 }
1028 
1029 void XmlFilterBase::setDiagramFontHeights(NamedShapePairs* pDiagramFontHeights)
1030 {
1031     mxImpl->mpDiagramFontHeights = pDiagramFontHeights;
1032 }
1033 
1034 NamedShapePairs* XmlFilterBase::getDiagramFontHeights() { return mxImpl->mpDiagramFontHeights; }
1035 
1036 OUString XmlFilterBase::getNamespaceURL(sal_Int32 nNSID) const
1037 {
1038     auto itr = mxImpl->mrNamespaceMap.maTransitionalNamespaceMap.find(nNSID);
1039     if (itr == mxImpl->mrNamespaceMap.maTransitionalNamespaceMap.end())
1040     {
1041         SAL_WARN("oox", "missing namespace in the namespace map for : " << nNSID);
1042         return OUString();
1043     }
1044 
1045     return itr->second;
1046 }
1047 
1048 void XmlFilterBase::importCustomFragments(css::uno::Reference<css::embed::XStorage> const & xDocumentStorage)
1049 {
1050     Reference<XRelationshipAccess> xRelations(xDocumentStorage, UNO_QUERY);
1051     if (!xRelations.is())
1052         return;
1053 
1054     const uno::Sequence<uno::Sequence<beans::StringPair>> aSeqs = xRelations->getAllRelationships();
1055 
1056     std::vector<StreamDataSequence> aCustomFragments;
1057     std::vector<OUString> aCustomFragmentTypes;
1058     std::vector<OUString> aCustomFragmentTargets;
1059     for (const uno::Sequence<beans::StringPair>& aSeq : aSeqs)
1060     {
1061         OUString sType;
1062         OUString sTarget;
1063         for (const beans::StringPair& aPair : aSeq)
1064         {
1065             if (aPair.First == "Target")
1066                 sTarget = aPair.Second;
1067             else if (aPair.First == "Type")
1068                 sType = aPair.Second;
1069         }
1070 
1071         // Preserve non-standard (i.e. custom) entries.
1072         if (!sType.match("http://schemas.openxmlformats.org") // OOXML/ECMA Transitional
1073             && !sType.match("http://purl.oclc.org")) // OOXML Strict
1074         {
1075             StreamDataSequence aDataSeq;
1076             if (importBinaryData(aDataSeq, sTarget))
1077             {
1078                 aCustomFragments.emplace_back(aDataSeq);
1079                 aCustomFragmentTypes.emplace_back(sType);
1080                 aCustomFragmentTargets.emplace_back(sTarget);
1081             }
1082         }
1083     }
1084 
1085     // Adding the saved custom xml DOM
1086     comphelper::SequenceAsHashMap aGrabBagProperties;
1087     aGrabBagProperties["OOXCustomFragments"] <<= comphelper::containerToSequence(aCustomFragments);
1088     aGrabBagProperties["OOXCustomFragmentTypes"] <<= comphelper::containerToSequence(aCustomFragmentTypes);
1089     aGrabBagProperties["OOXCustomFragmentTargets"] <<= comphelper::containerToSequence(aCustomFragmentTargets);
1090 
1091     std::vector<uno::Reference<xml::dom::XDocument>> aCustomXmlDomList;
1092     std::vector<uno::Reference<xml::dom::XDocument>> aCustomXmlDomPropsList;
1093     //FIXME: Ideally, we should get these the relations, but it seems that is not consistently set.
1094     // In some cases it's stored in the workbook relationships, which is unexpected. So we discover them directly.
1095     for (int i = 1; ; ++i)
1096     {
1097         Reference<XDocument> xCustDoc = importFragment("customXml/item" + OUString::number(i) + ".xml");
1098         Reference<XDocument> xCustDocProps = importFragment("customXml/itemProps" + OUString::number(i) + ".xml");
1099         if (xCustDoc && xCustDocProps)
1100         {
1101             aCustomXmlDomList.emplace_back(xCustDoc);
1102             aCustomXmlDomPropsList.emplace_back(xCustDocProps);
1103         }
1104         else
1105             break;
1106     }
1107 
1108     // Adding the saved custom xml DOM
1109     aGrabBagProperties["OOXCustomXml"] <<= comphelper::containerToSequence(aCustomXmlDomList);
1110     aGrabBagProperties["OOXCustomXmlProps"] <<= comphelper::containerToSequence(aCustomXmlDomPropsList);
1111 
1112     // Save the [Content_Types].xml after parsing.
1113     uno::Sequence<uno::Sequence<beans::StringPair>> aContentTypeInfo;
1114     uno::Reference<io::XInputStream> xInputStream = openInputStream("[Content_Types].xml");
1115     if (xInputStream.is())
1116         aContentTypeInfo = comphelper::OFOPXMLHelper::ReadContentTypeSequence(xInputStream, getComponentContext());
1117 
1118     aGrabBagProperties["OOXContentTypes"] <<= aContentTypeInfo;
1119 
1120     Reference<XComponent> xModel = getModel();
1121     oox::core::XmlFilterBase::putPropertiesToDocumentGrabBag(xModel, aGrabBagProperties);
1122 }
1123 
1124 void XmlFilterBase::exportCustomFragments()
1125 {
1126     Reference<XComponent> xModel = getModel();
1127     uno::Reference<beans::XPropertySet> xPropSet(xModel, uno::UNO_QUERY_THROW);
1128 
1129     uno::Reference<beans::XPropertySetInfo> xPropSetInfo = xPropSet->getPropertySetInfo();
1130     static constexpr OUStringLiteral aName = u"" UNO_NAME_MISC_OBJ_INTEROPGRABBAG;
1131     if (!xPropSetInfo->hasPropertyByName(aName))
1132         return;
1133 
1134     uno::Sequence<uno::Reference<xml::dom::XDocument>> customXmlDomlist;
1135     uno::Sequence<uno::Reference<xml::dom::XDocument>> customXmlDomPropslist;
1136     uno::Sequence<StreamDataSequence> customFragments;
1137     uno::Sequence<OUString> customFragmentTypes;
1138     uno::Sequence<OUString> customFragmentTargets;
1139     uno::Sequence<uno::Sequence<beans::StringPair>> aContentTypes;
1140 
1141     uno::Sequence<beans::PropertyValue> propList;
1142     xPropSet->getPropertyValue(aName) >>= propList;
1143     for (const auto& rProp : std::as_const(propList))
1144     {
1145         const OUString propName = rProp.Name;
1146         if (propName == "OOXCustomXml")
1147         {
1148             rProp.Value >>= customXmlDomlist;
1149         }
1150         else if (propName == "OOXCustomXmlProps")
1151         {
1152             rProp.Value >>= customXmlDomPropslist;
1153         }
1154         else if (propName == "OOXCustomFragments")
1155         {
1156             rProp.Value >>= customFragments;
1157         }
1158         else if (propName == "OOXCustomFragmentTypes")
1159         {
1160             rProp.Value >>= customFragmentTypes;
1161         }
1162         else if (propName == "OOXCustomFragmentTargets")
1163         {
1164             rProp.Value >>= customFragmentTargets;
1165         }
1166         else if (propName == "OOXContentTypes")
1167         {
1168             rProp.Value >>= aContentTypes;
1169         }
1170     }
1171 
1172     // Expect customXmlDomPropslist.getLength() == customXmlDomlist.getLength().
1173     for (sal_Int32 j = 0; j < customXmlDomlist.getLength(); j++)
1174     {
1175         uno::Reference<xml::dom::XDocument> customXmlDom = customXmlDomlist[j];
1176         uno::Reference<xml::dom::XDocument> customXmlDomProps = customXmlDomPropslist[j];
1177         const OUString fragmentPath = "customXml/item" + OUString::number((j+1)) + ".xml";
1178         if (customXmlDom.is())
1179         {
1180             addRelation(oox::getRelationship(Relationship::CUSTOMXML), "../" + fragmentPath);
1181 
1182             uno::Reference<xml::sax::XSAXSerializable> serializer(customXmlDom, uno::UNO_QUERY);
1183             uno::Reference<xml::sax::XWriter> writer = xml::sax::Writer::create(comphelper::getProcessComponentContext());
1184             writer->setOutputStream(openFragmentStream(fragmentPath, "application/xml"));
1185             serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
1186                                   uno::Sequence<beans::StringPair>());
1187         }
1188 
1189         if (customXmlDomProps.is())
1190         {
1191             uno::Reference<xml::sax::XSAXSerializable> serializer(customXmlDomProps, uno::UNO_QUERY);
1192             uno::Reference<xml::sax::XWriter> writer = xml::sax::Writer::create(comphelper::getProcessComponentContext());
1193             writer->setOutputStream(openFragmentStream("customXml/itemProps"+OUString::number((j+1))+".xml",
1194                                     "application/vnd.openxmlformats-officedocument.customXmlProperties+xml"));
1195             serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
1196                                   uno::Sequence<beans::StringPair>());
1197 
1198             // Adding itemprops's relationship entry to item.xml.rels file
1199             addRelation(openFragmentStream(fragmentPath, "application/xml"),
1200                         oox::getRelationship(Relationship::CUSTOMXMLPROPS),
1201                         "itemProps"+OUString::number((j+1))+".xml");
1202         }
1203     }
1204 
1205     // Expect customFragments.getLength() == customFragmentTypes.getLength() == customFragmentTargets.getLength().
1206     for (sal_Int32 j = 0; j < customFragments.getLength(); j++)
1207     {
1208         addRelation(customFragmentTypes[j], customFragmentTargets[j]);
1209         const OUString aFilename = customFragmentTargets[j];
1210         Reference<XOutputStream> xOutStream = openOutputStream(aFilename);
1211         if (xOutStream.is())
1212         {
1213             xOutStream->writeBytes(customFragments[j]);
1214             uno::Reference<XPropertySet> xProps(xOutStream, uno::UNO_QUERY);
1215             if (xProps.is())
1216             {
1217                 const OUString aType = comphelper::OFOPXMLHelper::GetContentTypeByName(aContentTypes, aFilename);
1218                 const OUString aContentType = (aType.getLength() ? aType : OUString("application/octet-stream"));
1219                 xProps->setPropertyValue("MediaType", uno::makeAny(aContentType));
1220             }
1221         }
1222     }
1223 }
1224 
1225 } // namespace oox::core
1226 
1227 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1228