1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
21 #include <comphelper/ofopxmlhelper.hxx>
22 #include <comphelper/attributelist.hxx>
24 #include <cppuhelper/implbase.hxx>
25 #include <rtl/ref.hxx>
27 #include <com/sun/star/beans/StringPair.hpp>
28 #include <com/sun/star/xml/sax/Parser.hpp>
29 #include <com/sun/star/xml/sax/XDocumentHandler.hpp>
30 #include <com/sun/star/xml/sax/SAXException.hpp>
31 #include <com/sun/star/xml/sax/Writer.hpp>
32 #include <com/sun/star/lang/IllegalArgumentException.hpp>
35 #define RELATIONINFO_FORMAT 0
36 #define CONTENTTYPE_FORMAT 1
37 #define FORMAT_MAX_ID CONTENTTYPE_FORMAT
39 using namespace ::com::sun::star
;
41 namespace comphelper
{
45 // this helper class is designed to allow to parse ContentType- and Relationship-related information from OfficeOpenXML format
46 class OFOPXMLHelper_Impl
47 : public cppu::WeakImplHelper
< css::xml::sax::XDocumentHandler
>
49 sal_uInt16
const m_nFormat
; // which format to parse
51 css::uno::Sequence
< css::uno::Sequence
< css::beans::StringPair
> > m_aResultSeq
;
52 std::vector
< OUString
> m_aElementsSeq
; // stack of elements being parsed
56 css::uno::Sequence
< css::uno::Sequence
< css::beans::StringPair
> > const & GetParsingResult() const;
58 explicit OFOPXMLHelper_Impl( sal_uInt16 nFormat
); // must not be created directly
61 virtual void SAL_CALL
startDocument() override
;
62 virtual void SAL_CALL
endDocument() override
;
63 virtual void SAL_CALL
startElement( const OUString
& aName
, const css::uno::Reference
< css::xml::sax::XAttributeList
>& xAttribs
) override
;
64 virtual void SAL_CALL
endElement( const OUString
& aName
) override
;
65 virtual void SAL_CALL
characters( const OUString
& aChars
) override
;
66 virtual void SAL_CALL
ignorableWhitespace( const OUString
& aWhitespaces
) override
;
67 virtual void SAL_CALL
processingInstruction( const OUString
& aTarget
, const OUString
& aData
) override
;
68 virtual void SAL_CALL
setDocumentLocator( const css::uno::Reference
< css::xml::sax::XLocator
>& xLocator
) override
;
73 namespace OFOPXMLHelper
{
75 /// @throws css::uno::Exception
76 static uno::Sequence
<uno::Sequence
< beans::StringPair
>> ReadSequence_Impl(
77 const uno::Reference
<io::XInputStream
>& xInStream
,
78 const OUString
& aStringID
, sal_uInt16 nFormat
,
79 const uno::Reference
<uno::XComponentContext
>& xContext
);
81 uno::Sequence
< uno::Sequence
< beans::StringPair
> > ReadRelationsInfoSequence(
82 const uno::Reference
< io::XInputStream
>& xInStream
,
83 std::u16string_view aStreamName
,
84 const uno::Reference
< uno::XComponentContext
>& rContext
)
86 OUString aStringID
= OUString::Concat("_rels/") + aStreamName
;
87 return ReadSequence_Impl( xInStream
, aStringID
, RELATIONINFO_FORMAT
, rContext
);
91 uno::Sequence
< uno::Sequence
< beans::StringPair
> > ReadContentTypeSequence(
92 const uno::Reference
< io::XInputStream
>& xInStream
,
93 const uno::Reference
< uno::XComponentContext
>& rContext
)
95 return ReadSequence_Impl( xInStream
, "[Content_Types].xml", CONTENTTYPE_FORMAT
, rContext
);
98 OUString
GetContentTypeByName(
99 const css::uno::Sequence
<css::uno::Sequence
<css::beans::StringPair
>>& rContentTypes
,
100 const OUString
& rFilename
)
102 if (rContentTypes
.getLength() < 2)
107 const uno::Sequence
<beans::StringPair
>& rDefaults
= rContentTypes
[0];
108 const uno::Sequence
<beans::StringPair
>& rOverrides
= rContentTypes
[1];
110 // Find the extension and use it to get the type.
111 const sal_Int32 nDotOffset
= rFilename
.lastIndexOf('.');
112 const OUString aExt
= (nDotOffset
>= 0 ? rFilename
.copy(nDotOffset
+ 1) : rFilename
); // Skip the dot.
114 const std::vector
<OUString
> aNames
= { aExt
, "/" + rFilename
};
115 for (const OUString
& aName
: aNames
)
117 const auto it1
= std::find_if(rOverrides
.begin(), rOverrides
.end(), [&aName
](const beans::StringPair
& rPair
)
118 { return rPair
.First
== aName
; });
119 if (it1
!= rOverrides
.end())
122 const auto it2
= std::find_if(rDefaults
.begin(), rDefaults
.end(), [&aName
](const beans::StringPair
& rPair
)
123 { return rPair
.First
== aName
; });
124 if (it2
!= rDefaults
.end())
131 void WriteRelationsInfoSequence(
132 const uno::Reference
< io::XOutputStream
>& xOutStream
,
133 const uno::Sequence
< uno::Sequence
< beans::StringPair
> >& aSequence
,
134 const uno::Reference
< uno::XComponentContext
>& rContext
)
136 if ( !xOutStream
.is() )
137 throw uno::RuntimeException();
139 uno::Reference
< css::xml::sax::XWriter
> xWriter
= css::xml::sax::Writer::create(rContext
);
141 xWriter
->setOutputStream( xOutStream
);
143 OUString
aRelListElement( "Relationships" );
144 OUString
aRelElement( "Relationship" );
145 OUString
aWhiteSpace( " " );
147 // write the namespace
148 rtl::Reference
<AttributeList
> pRootAttrList
= new AttributeList
;
149 pRootAttrList
->AddAttribute(
151 "http://schemas.openxmlformats.org/package/2006/relationships" );
153 xWriter
->startDocument();
154 xWriter
->startElement( aRelListElement
, pRootAttrList
);
156 for ( const auto & i
: aSequence
)
158 rtl::Reference
<AttributeList
> pAttrList
= new AttributeList
;
159 for( const beans::StringPair
& pair
: i
)
161 if ( !(pair
.First
== "Id"
162 || pair
.First
== "Type"
163 || pair
.First
== "TargetMode"
164 || pair
.First
== "Target") )
166 // TODO/LATER: should the extensions be allowed?
167 throw lang::IllegalArgumentException();
169 pAttrList
->AddAttribute( pair
.First
, pair
.Second
);
172 xWriter
->startElement( aRelElement
, pAttrList
);
173 xWriter
->ignorableWhitespace( aWhiteSpace
);
174 xWriter
->endElement( aRelElement
);
177 xWriter
->ignorableWhitespace( aWhiteSpace
);
178 xWriter
->endElement( aRelListElement
);
179 xWriter
->endDocument();
183 void WriteContentSequence(
184 const uno::Reference
< io::XOutputStream
>& xOutStream
,
185 const uno::Sequence
< beans::StringPair
>& aDefaultsSequence
,
186 const uno::Sequence
< beans::StringPair
>& aOverridesSequence
,
187 const uno::Reference
< uno::XComponentContext
>& rContext
)
189 if ( !xOutStream
.is() )
190 throw uno::RuntimeException();
192 uno::Reference
< css::xml::sax::XWriter
> xWriter
= css::xml::sax::Writer::create(rContext
);
194 xWriter
->setOutputStream( xOutStream
);
196 static constexpr OUStringLiteral
aTypesElement(u
"Types");
197 static constexpr OUStringLiteral
aDefaultElement(u
"Default");
198 static constexpr OUStringLiteral
aOverrideElement(u
"Override");
199 static constexpr OUStringLiteral
aContentTypeAttr(u
"ContentType");
200 static constexpr OUStringLiteral
aWhiteSpace(u
" ");
202 // write the namespace
203 rtl::Reference
<AttributeList
> pRootAttrList
= new AttributeList
;
204 pRootAttrList
->AddAttribute(
206 "http://schemas.openxmlformats.org/package/2006/content-types" );
208 xWriter
->startDocument();
209 xWriter
->startElement( aTypesElement
, pRootAttrList
);
211 for ( const beans::StringPair
& pair
: aDefaultsSequence
)
213 rtl::Reference
<AttributeList
> pAttrList
= new AttributeList
;
214 pAttrList
->AddAttribute( "Extension", pair
.First
);
215 pAttrList
->AddAttribute( aContentTypeAttr
, pair
.Second
);
217 xWriter
->startElement( aDefaultElement
, pAttrList
);
218 xWriter
->ignorableWhitespace( aWhiteSpace
);
219 xWriter
->endElement( aDefaultElement
);
222 for ( const beans::StringPair
& pair
: aOverridesSequence
)
224 rtl::Reference
<AttributeList
> pAttrList
= new AttributeList
;
225 pAttrList
->AddAttribute( "PartName", pair
.First
);
226 pAttrList
->AddAttribute( aContentTypeAttr
, pair
.Second
);
228 xWriter
->startElement( aOverrideElement
, pAttrList
);
229 xWriter
->ignorableWhitespace( aWhiteSpace
);
230 xWriter
->endElement( aOverrideElement
);
233 xWriter
->ignorableWhitespace( aWhiteSpace
);
234 xWriter
->endElement( aTypesElement
);
235 xWriter
->endDocument();
239 uno::Sequence
< uno::Sequence
< beans::StringPair
> > ReadSequence_Impl(
240 const uno::Reference
< io::XInputStream
>& xInStream
,
241 const OUString
& aStringID
, sal_uInt16 nFormat
,
242 const uno::Reference
< uno::XComponentContext
>& rContext
)
244 if ( !rContext
.is() || !xInStream
.is() || nFormat
> FORMAT_MAX_ID
)
245 throw uno::RuntimeException();
247 uno::Reference
< css::xml::sax::XParser
> xParser
= css::xml::sax::Parser::create( rContext
);
249 rtl::Reference
<OFOPXMLHelper_Impl
> pHelper
= new OFOPXMLHelper_Impl( nFormat
);
250 css::xml::sax::InputSource aParserInput
;
251 aParserInput
.aInputStream
= xInStream
;
252 aParserInput
.sSystemId
= aStringID
;
253 xParser
->setDocumentHandler( pHelper
);
254 xParser
->parseStream( aParserInput
);
255 xParser
->setDocumentHandler( uno::Reference
< css::xml::sax::XDocumentHandler
> () );
257 return pHelper
->GetParsingResult();
260 } // namespace OFOPXMLHelper
262 // Relations info related strings
263 constexpr OUStringLiteral
g_aRelListElement(u
"Relationships");
264 constexpr OUStringLiteral
g_aRelElement( u
"Relationship" );
265 constexpr OUStringLiteral
g_aIDAttr( u
"Id" );
266 constexpr OUStringLiteral
g_aTypeAttr( u
"Type" );
267 constexpr OUStringLiteral
g_aTargetModeAttr( u
"TargetMode" );
268 constexpr OUStringLiteral
g_aTargetAttr( u
"Target" );
270 // ContentType related strings
271 constexpr OUStringLiteral
g_aTypesElement( u
"Types" );
272 constexpr OUStringLiteral
g_aDefaultElement( u
"Default" );
273 constexpr OUStringLiteral
g_aOverrideElement( u
"Override" );
274 constexpr OUStringLiteral
g_aExtensionAttr( u
"Extension" );
275 constexpr OUStringLiteral
g_aPartNameAttr( u
"PartName" );
276 constexpr OUStringLiteral
g_aContentTypeAttr( u
"ContentType" );
278 OFOPXMLHelper_Impl::OFOPXMLHelper_Impl( sal_uInt16 nFormat
)
279 : m_nFormat( nFormat
)
283 uno::Sequence
< uno::Sequence
< beans::StringPair
> > const & OFOPXMLHelper_Impl::GetParsingResult() const
285 if ( !m_aElementsSeq
.empty() )
286 throw uno::RuntimeException(); // the parsing has still not finished!
292 void SAL_CALL
OFOPXMLHelper_Impl::startDocument()
297 void SAL_CALL
OFOPXMLHelper_Impl::endDocument()
302 void SAL_CALL
OFOPXMLHelper_Impl::startElement( const OUString
& aName
, const uno::Reference
< css::xml::sax::XAttributeList
>& xAttribs
)
304 if ( m_nFormat
== RELATIONINFO_FORMAT
)
306 if ( aName
== g_aRelListElement
)
308 sal_Int32 nNewLength
= m_aElementsSeq
.size() + 1;
310 if ( nNewLength
!= 1 )
311 throw css::xml::sax::SAXException(); // TODO: this element must be the first level element
313 m_aElementsSeq
.push_back( aName
);
315 return; // nothing to do
317 else if ( aName
== g_aRelElement
)
319 sal_Int32 nNewLength
= m_aElementsSeq
.size() + 1;
320 if ( nNewLength
!= 2 )
321 throw css::xml::sax::SAXException(); // TODO: this element must be the second level element
323 m_aElementsSeq
.push_back( aName
);
325 sal_Int32 nNewEntryNum
= m_aResultSeq
.getLength() + 1;
326 m_aResultSeq
.realloc( nNewEntryNum
);
327 auto pResultSeq
= m_aResultSeq
.getArray();
328 sal_Int32 nAttrNum
= 0;
329 pResultSeq
[nNewEntryNum
-1].realloc( 4 ); // the maximal expected number of arguments is 4
330 auto pAttrs
= pResultSeq
[nNewEntryNum
-1].getArray();
332 OUString aIDValue
= xAttribs
->getValueByName( g_aIDAttr
);
333 if ( aIDValue
.isEmpty() )
334 throw css::xml::sax::SAXException(); // TODO: the ID value must present
336 OUString aTypeValue
= xAttribs
->getValueByName( g_aTypeAttr
);
337 OUString aTargetValue
= xAttribs
->getValueByName( g_aTargetAttr
);
338 OUString aTargetModeValue
= xAttribs
->getValueByName( g_aTargetModeAttr
);
340 pAttrs
[++nAttrNum
- 1].First
= g_aIDAttr
;
341 pAttrs
[nAttrNum
- 1].Second
= aIDValue
;
343 if ( !aTypeValue
.isEmpty() )
345 pAttrs
[++nAttrNum
- 1].First
= g_aTypeAttr
;
346 pAttrs
[nAttrNum
- 1].Second
= aTypeValue
;
349 if ( !aTargetValue
.isEmpty() )
351 pAttrs
[++nAttrNum
- 1].First
= g_aTargetAttr
;
352 pAttrs
[nAttrNum
- 1].Second
= aTargetValue
;
355 if ( !aTargetModeValue
.isEmpty() )
357 pAttrs
[++nAttrNum
- 1].First
= g_aTargetModeAttr
;
358 pAttrs
[nAttrNum
- 1].Second
= aTargetModeValue
;
361 pResultSeq
[nNewEntryNum
-1].realloc( nAttrNum
);
364 throw css::xml::sax::SAXException(); // TODO: no other elements expected!
366 else if ( m_nFormat
== CONTENTTYPE_FORMAT
)
368 if ( aName
== g_aTypesElement
)
370 sal_Int32 nNewLength
= m_aElementsSeq
.size() + 1;
372 if ( nNewLength
!= 1 )
373 throw css::xml::sax::SAXException(); // TODO: this element must be the first level element
375 m_aElementsSeq
.push_back( aName
);
377 if ( !m_aResultSeq
.hasElements() )
378 m_aResultSeq
.realloc( 2 );
380 return; // nothing to do
382 else if ( aName
== g_aDefaultElement
)
384 sal_Int32 nNewLength
= m_aElementsSeq
.size() + 1;
385 if ( nNewLength
!= 2 )
386 throw css::xml::sax::SAXException(); // TODO: this element must be the second level element
388 m_aElementsSeq
.push_back( aName
);
390 if ( !m_aResultSeq
.hasElements() )
391 m_aResultSeq
.realloc( 2 );
393 if ( m_aResultSeq
.getLength() != 2 )
394 throw uno::RuntimeException();
396 auto pResultSeq
= m_aResultSeq
.getArray();
398 const OUString aExtensionValue
= xAttribs
->getValueByName( g_aExtensionAttr
);
399 if ( aExtensionValue
.isEmpty() )
400 throw css::xml::sax::SAXException(); // TODO: the Extension value must present
402 const OUString aContentTypeValue
= xAttribs
->getValueByName( g_aContentTypeAttr
);
403 if ( aContentTypeValue
.isEmpty() )
404 throw css::xml::sax::SAXException(); // TODO: the ContentType value must present
406 const sal_Int32 nNewResultLen
= m_aResultSeq
[0].getLength() + 1;
407 pResultSeq
[0].realloc( nNewResultLen
);
408 auto pSeq
= pResultSeq
[0].getArray();
410 pSeq
[nNewResultLen
-1].First
= aExtensionValue
;
411 pSeq
[nNewResultLen
-1].Second
= aContentTypeValue
;
413 else if ( aName
== g_aOverrideElement
)
415 sal_Int32 nNewLength
= m_aElementsSeq
.size() + 1;
416 if ( nNewLength
!= 2 )
417 throw css::xml::sax::SAXException(); // TODO: this element must be the second level element
419 m_aElementsSeq
.push_back( aName
);
421 if ( !m_aResultSeq
.hasElements() )
422 m_aResultSeq
.realloc( 2 );
424 if ( m_aResultSeq
.getLength() != 2 )
425 throw uno::RuntimeException();
427 auto pResultSeq
= m_aResultSeq
.getArray();
429 OUString aPartNameValue
= xAttribs
->getValueByName( g_aPartNameAttr
);
430 if ( aPartNameValue
.isEmpty() )
431 throw css::xml::sax::SAXException(); // TODO: the PartName value must present
433 OUString aContentTypeValue
= xAttribs
->getValueByName( g_aContentTypeAttr
);
434 if ( aContentTypeValue
.isEmpty() )
435 throw css::xml::sax::SAXException(); // TODO: the ContentType value must present
437 sal_Int32 nNewResultLen
= m_aResultSeq
[1].getLength() + 1;
438 pResultSeq
[1].realloc( nNewResultLen
);
439 auto pSeq
= pResultSeq
[1].getArray();
441 pSeq
[nNewResultLen
-1].First
= aPartNameValue
;
442 pSeq
[nNewResultLen
-1].Second
= aContentTypeValue
;
445 throw css::xml::sax::SAXException(); // TODO: no other elements expected!
448 throw css::xml::sax::SAXException(); // TODO: no other elements expected!
452 void SAL_CALL
OFOPXMLHelper_Impl::endElement( const OUString
& aName
)
454 if ( m_nFormat
== RELATIONINFO_FORMAT
|| m_nFormat
== CONTENTTYPE_FORMAT
)
456 sal_Int32 nLength
= m_aElementsSeq
.size();
458 throw css::xml::sax::SAXException(); // TODO: no other end elements expected!
460 if ( m_aElementsSeq
[nLength
-1] != aName
)
461 throw css::xml::sax::SAXException(); // TODO: unexpected element ended
463 m_aElementsSeq
.resize( nLength
- 1 );
468 void SAL_CALL
OFOPXMLHelper_Impl::characters( const OUString
& /*aChars*/ )
473 void SAL_CALL
OFOPXMLHelper_Impl::ignorableWhitespace( const OUString
& /*aWhitespaces*/ )
478 void SAL_CALL
OFOPXMLHelper_Impl::processingInstruction( const OUString
& /*aTarget*/, const OUString
& /*aData*/ )
483 void SAL_CALL
OFOPXMLHelper_Impl::setDocumentLocator( const uno::Reference
< css::xml::sax::XLocator
>& /*xLocator*/ )
487 } // namespace comphelper
489 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */