xref: /core/oox/source/core/xmlfilterbase.cxx (revision d50501d1)
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                      OUStringToOString( aFragmentPath, RTL_TEXTENCODING_ASCII_US ) + "'" ).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, std::u16string_view 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, std::u16string_view 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, std::u16string_view 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( " " + 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, u"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             u"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             u"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             // tdf#127864 - export custom document properties using utf8 text encoding
862             OString aName = OUStringToOString(rProp.Name, RTL_TEXTENCODING_UTF8);
863             // Skip storing these values in Custom Properties as it will be stored in Core/Extended Properties
864             if (( aName == "OOXMLCorePropertyCategory" ) || // stored in cp:category
865                 ( aName == "OOXMLCorePropertyContentStatus" ) || // stored in cp:contentStatus
866                 ( aName == "OOXMLCorePropertyContentType" ) || // stored in cp:contentType
867                 ( aName == "OOXMLCorePropertyIdentifier" ) || // stored in dc:identifier
868                 ( aName == "OOXMLCorePropertyVersion" ) || // stored in cp:version
869                 ( aName == "HyperlinkBase" ) || // stored in Extended File Properties
870                 ( aName == "AppVersion" ) || // stored in Extended File Properties
871                 ( aName == "DocSecurity" ) || // stored in Extended File Properties
872                 ( aName == "Manager" ) || // stored in Extended File Properties
873                 ( aName == "Company" )) // stored in Extended File Properties
874                 continue;
875 
876             // pid starts from 2 not from 1 as MS supports pid from 2
877             pAppProps->startElement( XML_property ,
878                 XML_fmtid,  "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
879                 XML_pid,    OString::number(nIndex + 2),
880                 XML_name,   aName);
881 
882             switch ( rProp.Value.getValueTypeClass() )
883             {
884                 case TypeClass_STRING:
885                 {
886                     OUString aValue;
887                     rProp.Value >>= aValue;
888                     writeElement( pAppProps, FSNS( XML_vt, XML_lpwstr ), aValue );
889                 }
890                 break;
891                 case TypeClass_BOOLEAN:
892                 {
893                     bool val = *o3tl::forceAccess<bool>(rProp.Value);
894                     writeElement( pAppProps, FSNS( XML_vt, XML_bool ), val ? 1 : 0);
895                 }
896                 break;
897                 case TypeClass_DOUBLE:
898                 {
899                     double num = {}; // spurious -Werror=maybe-uninitialized
900                     if ( rProp.Value >>= num )
901                     {
902                         // r8 - 8-byte real number
903                         writeElement( pAppProps, FSNS( XML_vt, XML_r8 ), OUString::number(num) );
904                     }
905                 }
906                 break;
907                 default:
908                 {
909                     double num = {}; // spurious -Werror=maybe-uninitialized
910                     util::Date aDate;
911                     util::Duration aDuration;
912                     util::DateTime aDateTime;
913                     if ( rProp.Value >>= num )
914                     {
915                         // i4 - 4-byte signed integer
916                         writeElement( pAppProps, FSNS( XML_vt, XML_i4 ), num );
917                     }
918                     else if ( rProp.Value >>= aDate )
919                     {
920                         aDateTime = util::DateTime( 0, 0 , 0, 0, aDate.Day, aDate.Month, aDate.Year, true );
921                         writeElement( pAppProps, FSNS( XML_vt, XML_filetime ), aDateTime);
922                     }
923                     else if ( rProp.Value >>= aDuration )
924                     {
925                         OUStringBuffer buf;
926                         ::sax::Converter::convertDuration( buf, aDuration );
927                         OUString aDurationStr = buf.makeStringAndClear();
928                         writeElement( pAppProps, FSNS( XML_vt, XML_lpwstr ), aDurationStr );
929                     }
930                     else if ( rProp.Value >>= aDateTime )
931                             writeElement( pAppProps, FSNS( XML_vt, XML_filetime ), aDateTime );
932                     else
933                         //no other options
934                         OSL_FAIL( "XMLFilterBase::writeCustomProperties unsupported value type!" );
935                  }
936                  break;
937             }
938             pAppProps->endElement( XML_property );
939         }
940         ++nIndex;
941     }
942     pAppProps->endElement( XML_Properties );
943 }
944 
945 void XmlFilterBase::exportDocumentProperties( const Reference< XDocumentProperties >& xProperties, bool bSecurityOptOpenReadOnly )
946 {
947     if( xProperties.is() )
948     {
949         writeCoreProperties( *this, xProperties );
950         writeAppProperties( *this, xProperties );
951         writeCustomProperties( *this, xProperties, bSecurityOptOpenReadOnly );
952     }
953 }
954 
955 // protected ------------------------------------------------------------------
956 
957 Reference< XInputStream > XmlFilterBase::implGetInputStream( MediaDescriptor& rMediaDesc ) const
958 {
959     /*  Get the input stream directly from the media descriptor, or decrypt the
960         package again. The latter is needed e.g. when the document is reloaded.
961         All this is implemented in the detector service. */
962     rtl::Reference< FilterDetect > xDetector( new FilterDetect( getComponentContext() ) );
963     return xDetector->extractUnencryptedPackage( rMediaDesc );
964 }
965 
966 Reference<XStream> XmlFilterBase::implGetOutputStream( MediaDescriptor& rMediaDescriptor ) const
967 {
968     const Sequence< NamedValue > aMediaEncData = rMediaDescriptor.getUnpackedValueOrDefault(
969                                         MediaDescriptor::PROP_ENCRYPTIONDATA(),
970                                         Sequence< NamedValue >() );
971 
972     if (aMediaEncData.getLength() == 0)
973     {
974         return FilterBase::implGetOutputStream( rMediaDescriptor );
975     }
976     else // We need to encrypt the stream so create a memory stream
977     {
978         Reference< XComponentContext > xContext = getComponentContext();
979         return Reference< XStream > (
980                     xContext->getServiceManager()->createInstanceWithContext("com.sun.star.comp.MemoryStream", xContext),
981                     uno::UNO_QUERY_THROW );
982     }
983 }
984 
985 bool XmlFilterBase::implFinalizeExport( MediaDescriptor& rMediaDescriptor )
986 {
987     bool bRet = true;
988 
989     const Sequence< NamedValue > aMediaEncData = rMediaDescriptor.getUnpackedValueOrDefault(
990                                         MediaDescriptor::PROP_ENCRYPTIONDATA(),
991                                         Sequence< NamedValue >() );
992 
993     if (aMediaEncData.getLength())
994     {
995         commitStorage();
996 
997         Reference< XStream> xDocumentStream (FilterBase::implGetOutputStream(rMediaDescriptor));
998         oox::ole::OleStorage aOleStorage( getComponentContext(), xDocumentStream, true );
999         crypto::DocumentEncryption encryptor( getComponentContext(), getMainDocumentStream(), aOleStorage, aMediaEncData );
1000         bRet = encryptor.encrypt();
1001         if (bRet)
1002             aOleStorage.commit();
1003     }
1004 
1005     return bRet;
1006 }
1007 
1008 // private --------------------------------------------------------------------
1009 
1010 StorageRef XmlFilterBase::implCreateStorage( const Reference< XInputStream >& rxInStream ) const
1011 {
1012     return std::make_shared<ZipStorage>( getComponentContext(), rxInStream );
1013 }
1014 
1015 StorageRef XmlFilterBase::implCreateStorage( const Reference< XStream >& rxOutStream ) const
1016 {
1017     return std::make_shared<ZipStorage>( getComponentContext(), rxOutStream );
1018 }
1019 
1020 bool XmlFilterBase::isMSO2007Document() const
1021 {
1022     return mbMSO2007;
1023 }
1024 
1025 void XmlFilterBase::setMissingExtDrawing()
1026 {
1027     mbMissingExtDrawing = true;
1028 }
1029 
1030 void XmlFilterBase::setDiagramFontHeights(NamedShapePairs* pDiagramFontHeights)
1031 {
1032     mxImpl->mpDiagramFontHeights = pDiagramFontHeights;
1033 }
1034 
1035 NamedShapePairs* XmlFilterBase::getDiagramFontHeights() { return mxImpl->mpDiagramFontHeights; }
1036 
1037 OUString XmlFilterBase::getNamespaceURL(sal_Int32 nNSID) const
1038 {
1039     auto itr = mxImpl->mrNamespaceMap.maTransitionalNamespaceMap.find(nNSID);
1040     if (itr == mxImpl->mrNamespaceMap.maTransitionalNamespaceMap.end())
1041     {
1042         SAL_WARN("oox", "missing namespace in the namespace map for : " << nNSID);
1043         return OUString();
1044     }
1045 
1046     return itr->second;
1047 }
1048 
1049 void XmlFilterBase::importCustomFragments(css::uno::Reference<css::embed::XStorage> const & xDocumentStorage)
1050 {
1051     Reference<XRelationshipAccess> xRelations(xDocumentStorage, UNO_QUERY);
1052     if (!xRelations.is())
1053         return;
1054 
1055     const uno::Sequence<uno::Sequence<beans::StringPair>> aSeqs = xRelations->getAllRelationships();
1056 
1057     std::vector<StreamDataSequence> aCustomFragments;
1058     std::vector<OUString> aCustomFragmentTypes;
1059     std::vector<OUString> aCustomFragmentTargets;
1060     for (const uno::Sequence<beans::StringPair>& aSeq : aSeqs)
1061     {
1062         OUString sType;
1063         OUString sTarget;
1064         for (const beans::StringPair& aPair : aSeq)
1065         {
1066             if (aPair.First == "Target")
1067                 sTarget = aPair.Second;
1068             else if (aPair.First == "Type")
1069                 sType = aPair.Second;
1070         }
1071 
1072         // Preserve non-standard (i.e. custom) entries.
1073         if (!sType.match("http://schemas.openxmlformats.org") // OOXML/ECMA Transitional
1074             && !sType.match("http://purl.oclc.org")) // OOXML Strict
1075         {
1076             StreamDataSequence aDataSeq;
1077             if (importBinaryData(aDataSeq, sTarget))
1078             {
1079                 aCustomFragments.emplace_back(aDataSeq);
1080                 aCustomFragmentTypes.emplace_back(sType);
1081                 aCustomFragmentTargets.emplace_back(sTarget);
1082             }
1083         }
1084     }
1085 
1086     // Adding the saved custom xml DOM
1087     comphelper::SequenceAsHashMap aGrabBagProperties;
1088     aGrabBagProperties["OOXCustomFragments"] <<= comphelper::containerToSequence(aCustomFragments);
1089     aGrabBagProperties["OOXCustomFragmentTypes"] <<= comphelper::containerToSequence(aCustomFragmentTypes);
1090     aGrabBagProperties["OOXCustomFragmentTargets"] <<= comphelper::containerToSequence(aCustomFragmentTargets);
1091 
1092     std::vector<uno::Reference<xml::dom::XDocument>> aCustomXmlDomList;
1093     std::vector<uno::Reference<xml::dom::XDocument>> aCustomXmlDomPropsList;
1094     //FIXME: Ideally, we should get these the relations, but it seems that is not consistently set.
1095     // In some cases it's stored in the workbook relationships, which is unexpected. So we discover them directly.
1096     for (int i = 1; ; ++i)
1097     {
1098         Reference<XDocument> xCustDoc = importFragment("customXml/item" + OUString::number(i) + ".xml");
1099         Reference<XDocument> xCustDocProps = importFragment("customXml/itemProps" + OUString::number(i) + ".xml");
1100         if (xCustDoc && xCustDocProps)
1101         {
1102             aCustomXmlDomList.emplace_back(xCustDoc);
1103             aCustomXmlDomPropsList.emplace_back(xCustDocProps);
1104         }
1105         else
1106             break;
1107     }
1108 
1109     // Adding the saved custom xml DOM
1110     aGrabBagProperties["OOXCustomXml"] <<= comphelper::containerToSequence(aCustomXmlDomList);
1111     aGrabBagProperties["OOXCustomXmlProps"] <<= comphelper::containerToSequence(aCustomXmlDomPropsList);
1112 
1113     // Save the [Content_Types].xml after parsing.
1114     uno::Sequence<uno::Sequence<beans::StringPair>> aContentTypeInfo;
1115     uno::Reference<io::XInputStream> xInputStream = openInputStream("[Content_Types].xml");
1116     if (xInputStream.is())
1117         aContentTypeInfo = comphelper::OFOPXMLHelper::ReadContentTypeSequence(xInputStream, getComponentContext());
1118 
1119     aGrabBagProperties["OOXContentTypes"] <<= aContentTypeInfo;
1120 
1121     Reference<XComponent> xModel = getModel();
1122     oox::core::XmlFilterBase::putPropertiesToDocumentGrabBag(xModel, aGrabBagProperties);
1123 }
1124 
1125 void XmlFilterBase::exportCustomFragments()
1126 {
1127     Reference<XComponent> xModel = getModel();
1128     uno::Reference<beans::XPropertySet> xPropSet(xModel, uno::UNO_QUERY_THROW);
1129 
1130     uno::Reference<beans::XPropertySetInfo> xPropSetInfo = xPropSet->getPropertySetInfo();
1131     static constexpr OUStringLiteral aName = u"" UNO_NAME_MISC_OBJ_INTEROPGRABBAG;
1132     if (!xPropSetInfo->hasPropertyByName(aName))
1133         return;
1134 
1135     uno::Sequence<uno::Reference<xml::dom::XDocument>> customXmlDomlist;
1136     uno::Sequence<uno::Reference<xml::dom::XDocument>> customXmlDomPropslist;
1137     uno::Sequence<StreamDataSequence> customFragments;
1138     uno::Sequence<OUString> customFragmentTypes;
1139     uno::Sequence<OUString> customFragmentTargets;
1140     uno::Sequence<uno::Sequence<beans::StringPair>> aContentTypes;
1141 
1142     uno::Sequence<beans::PropertyValue> propList;
1143     xPropSet->getPropertyValue(aName) >>= propList;
1144     for (const auto& rProp : std::as_const(propList))
1145     {
1146         const OUString propName = rProp.Name;
1147         if (propName == "OOXCustomXml")
1148         {
1149             rProp.Value >>= customXmlDomlist;
1150         }
1151         else if (propName == "OOXCustomXmlProps")
1152         {
1153             rProp.Value >>= customXmlDomPropslist;
1154         }
1155         else if (propName == "OOXCustomFragments")
1156         {
1157             rProp.Value >>= customFragments;
1158         }
1159         else if (propName == "OOXCustomFragmentTypes")
1160         {
1161             rProp.Value >>= customFragmentTypes;
1162         }
1163         else if (propName == "OOXCustomFragmentTargets")
1164         {
1165             rProp.Value >>= customFragmentTargets;
1166         }
1167         else if (propName == "OOXContentTypes")
1168         {
1169             rProp.Value >>= aContentTypes;
1170         }
1171     }
1172 
1173     // Expect customXmlDomPropslist.getLength() == customXmlDomlist.getLength().
1174     for (sal_Int32 j = 0; j < customXmlDomlist.getLength(); j++)
1175     {
1176         uno::Reference<xml::dom::XDocument> customXmlDom = customXmlDomlist[j];
1177         uno::Reference<xml::dom::XDocument> customXmlDomProps = customXmlDomPropslist[j];
1178         const OUString fragmentPath = "customXml/item" + OUString::number(j+1) + ".xml";
1179         if (customXmlDom.is())
1180         {
1181             addRelation(oox::getRelationship(Relationship::CUSTOMXML), OUString("../" + fragmentPath));
1182 
1183             uno::Reference<xml::sax::XSAXSerializable> serializer(customXmlDom, uno::UNO_QUERY);
1184             uno::Reference<xml::sax::XWriter> writer = xml::sax::Writer::create(comphelper::getProcessComponentContext());
1185             writer->setOutputStream(openFragmentStream(fragmentPath, "application/xml"));
1186             serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
1187                                   uno::Sequence<beans::StringPair>());
1188         }
1189 
1190         if (customXmlDomProps.is())
1191         {
1192             uno::Reference<xml::sax::XSAXSerializable> serializer(customXmlDomProps, uno::UNO_QUERY);
1193             uno::Reference<xml::sax::XWriter> writer = xml::sax::Writer::create(comphelper::getProcessComponentContext());
1194             writer->setOutputStream(openFragmentStream("customXml/itemProps"+OUString::number(j+1)+".xml",
1195                                     "application/vnd.openxmlformats-officedocument.customXmlProperties+xml"));
1196             serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
1197                                   uno::Sequence<beans::StringPair>());
1198 
1199             // Adding itemprops's relationship entry to item.xml.rels file
1200             addRelation(openFragmentStream(fragmentPath, "application/xml"),
1201                         oox::getRelationship(Relationship::CUSTOMXMLPROPS),
1202                         OUString("itemProps"+OUString::number(j+1)+".xml"));
1203         }
1204     }
1205 
1206     // Expect customFragments.getLength() == customFragmentTypes.getLength() == customFragmentTargets.getLength().
1207     for (sal_Int32 j = 0; j < customFragments.getLength(); j++)
1208     {
1209         addRelation(customFragmentTypes[j], customFragmentTargets[j]);
1210         const OUString aFilename = customFragmentTargets[j];
1211         Reference<XOutputStream> xOutStream = openOutputStream(aFilename);
1212         if (xOutStream.is())
1213         {
1214             xOutStream->writeBytes(customFragments[j]);
1215             uno::Reference<XPropertySet> xProps(xOutStream, uno::UNO_QUERY);
1216             if (xProps.is())
1217             {
1218                 const OUString aType = comphelper::OFOPXMLHelper::GetContentTypeByName(aContentTypes, aFilename);
1219                 const OUString aContentType = (aType.getLength() ? aType : OUString("application/octet-stream"));
1220                 xProps->setPropertyValue("MediaType", uno::makeAny(aContentType));
1221             }
1222         }
1223     }
1224 }
1225 
1226 } // namespace oox::core
1227 
1228 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1229