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
, u
"[Content_Types].xml"_ustr
, 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("Invalid output stream");
139 uno::Reference
< css::xml::sax::XWriter
> xWriter
= css::xml::sax::Writer::create(rContext
);
141 xWriter
->setOutputStream( xOutStream
);
143 OUString
aRelListElement( u
"Relationships"_ustr
);
144 OUString
aRelElement( u
"Relationship"_ustr
);
145 OUString
aWhiteSpace( u
" "_ustr
);
147 // write the namespace
148 rtl::Reference
<AttributeList
> pRootAttrList
= new AttributeList
;
149 pRootAttrList
->AddAttribute(
151 u
"http://schemas.openxmlformats.org/package/2006/relationships"_ustr
);
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("Invalid output stream");
192 uno::Reference
< css::xml::sax::XWriter
> xWriter
= css::xml::sax::Writer::create(rContext
);
194 xWriter
->setOutputStream( xOutStream
);
196 static constexpr OUString
aTypesElement(u
"Types"_ustr
);
197 static constexpr OUString
aDefaultElement(u
"Default"_ustr
);
198 static constexpr OUString
aOverrideElement(u
"Override"_ustr
);
199 static constexpr OUString
aContentTypeAttr(u
"ContentType"_ustr
);
200 static constexpr OUString
aWhiteSpace(u
" "_ustr
);
202 // write the namespace
203 rtl::Reference
<AttributeList
> pRootAttrList
= new AttributeList
;
204 pRootAttrList
->AddAttribute(
206 u
"http://schemas.openxmlformats.org/package/2006/content-types"_ustr
);
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( u
"Extension"_ustr
, 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( u
"PartName"_ustr
, 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("Invalid input stream or context");
248 uno::Reference
< css::xml::sax::XParser
> xParser
= css::xml::sax::Parser::create( rContext
);
250 rtl::Reference
<OFOPXMLHelper_Impl
> pHelper
= new OFOPXMLHelper_Impl( nFormat
);
251 css::xml::sax::InputSource aParserInput
;
252 aParserInput
.aInputStream
= xInStream
;
253 aParserInput
.sSystemId
= aStringID
;
254 xParser
->setDocumentHandler( pHelper
);
255 xParser
->parseStream( aParserInput
);
256 xParser
->setDocumentHandler( uno::Reference
< css::xml::sax::XDocumentHandler
> () );
258 return pHelper
->GetParsingResult();
261 } // namespace OFOPXMLHelper
263 // Relations info related strings
264 constexpr OUStringLiteral
g_aRelListElement(u
"Relationships");
265 constexpr OUStringLiteral
g_aRelElement( u
"Relationship" );
266 constexpr OUString
g_aIDAttr( u
"Id"_ustr
);
267 constexpr OUString
g_aTypeAttr( u
"Type"_ustr
);
268 constexpr OUString
g_aTargetModeAttr( u
"TargetMode"_ustr
);
269 constexpr OUString
g_aTargetAttr( u
"Target"_ustr
);
271 // ContentType related strings
272 constexpr OUStringLiteral
g_aTypesElement( u
"Types" );
273 constexpr OUStringLiteral
g_aDefaultElement( u
"Default" );
274 constexpr OUStringLiteral
g_aOverrideElement( u
"Override" );
275 constexpr OUStringLiteral
g_aExtensionAttr( u
"Extension" );
276 constexpr OUStringLiteral
g_aPartNameAttr( u
"PartName" );
277 constexpr OUString
g_aContentTypeAttr( u
"ContentType"_ustr
);
279 OFOPXMLHelper_Impl::OFOPXMLHelper_Impl( sal_uInt16 nFormat
)
280 : m_nFormat( nFormat
)
284 uno::Sequence
< uno::Sequence
< beans::StringPair
> > const & OFOPXMLHelper_Impl::GetParsingResult() const
286 if ( !m_aElementsSeq
.empty() )
287 throw uno::RuntimeException(); // the parsing has still not finished!
293 void SAL_CALL
OFOPXMLHelper_Impl::startDocument()
298 void SAL_CALL
OFOPXMLHelper_Impl::endDocument()
303 void SAL_CALL
OFOPXMLHelper_Impl::startElement( const OUString
& aName
, const uno::Reference
< css::xml::sax::XAttributeList
>& xAttribs
)
305 if ( m_nFormat
== RELATIONINFO_FORMAT
)
307 if ( aName
== g_aRelListElement
)
309 sal_Int32 nNewLength
= m_aElementsSeq
.size() + 1;
311 if ( nNewLength
!= 1 )
312 throw css::xml::sax::SAXException(); // TODO: this element must be the first level element
314 m_aElementsSeq
.push_back( aName
);
316 return; // nothing to do
318 else if ( aName
== g_aRelElement
)
320 sal_Int32 nNewLength
= m_aElementsSeq
.size() + 1;
321 if ( nNewLength
!= 2 )
322 throw css::xml::sax::SAXException(); // TODO: this element must be the second level element
324 m_aElementsSeq
.push_back( aName
);
326 sal_Int32 nNewEntryNum
= m_aResultSeq
.getLength() + 1;
327 m_aResultSeq
.realloc( nNewEntryNum
);
328 auto pResultSeq
= m_aResultSeq
.getArray();
329 sal_Int32 nAttrNum
= 0;
330 pResultSeq
[nNewEntryNum
-1].realloc( 4 ); // the maximal expected number of arguments is 4
331 auto pAttrs
= pResultSeq
[nNewEntryNum
-1].getArray();
333 OUString aIDValue
= xAttribs
->getValueByName( g_aIDAttr
);
334 if ( aIDValue
.isEmpty() )
335 throw css::xml::sax::SAXException(); // TODO: the ID value must present
337 OUString aTypeValue
= xAttribs
->getValueByName( g_aTypeAttr
);
338 OUString aTargetValue
= xAttribs
->getValueByName( g_aTargetAttr
);
339 OUString aTargetModeValue
= xAttribs
->getValueByName( g_aTargetModeAttr
);
341 pAttrs
[++nAttrNum
- 1].First
= g_aIDAttr
;
342 pAttrs
[nAttrNum
- 1].Second
= aIDValue
;
344 if ( !aTypeValue
.isEmpty() )
346 pAttrs
[++nAttrNum
- 1].First
= g_aTypeAttr
;
347 pAttrs
[nAttrNum
- 1].Second
= aTypeValue
;
350 if ( !aTargetValue
.isEmpty() )
352 pAttrs
[++nAttrNum
- 1].First
= g_aTargetAttr
;
353 pAttrs
[nAttrNum
- 1].Second
= aTargetValue
;
356 if ( !aTargetModeValue
.isEmpty() )
358 pAttrs
[++nAttrNum
- 1].First
= g_aTargetModeAttr
;
359 pAttrs
[nAttrNum
- 1].Second
= aTargetModeValue
;
362 pResultSeq
[nNewEntryNum
-1].realloc( nAttrNum
);
365 throw css::xml::sax::SAXException(); // TODO: no other elements expected!
367 else if ( m_nFormat
== CONTENTTYPE_FORMAT
)
369 if ( aName
== g_aTypesElement
)
371 sal_Int32 nNewLength
= m_aElementsSeq
.size() + 1;
373 if ( nNewLength
!= 1 )
374 throw css::xml::sax::SAXException(); // TODO: this element must be the first level element
376 m_aElementsSeq
.push_back( aName
);
378 if ( !m_aResultSeq
.hasElements() )
379 m_aResultSeq
.realloc( 2 );
381 return; // nothing to do
383 else if ( aName
== g_aDefaultElement
)
385 sal_Int32 nNewLength
= m_aElementsSeq
.size() + 1;
386 if ( nNewLength
!= 2 )
387 throw css::xml::sax::SAXException(); // TODO: this element must be the second level element
389 m_aElementsSeq
.push_back( aName
);
391 if ( !m_aResultSeq
.hasElements() )
392 m_aResultSeq
.realloc( 2 );
394 if ( m_aResultSeq
.getLength() != 2 )
395 throw uno::RuntimeException("m_aResultSeq already has elements and is not reallocated to 2.");
397 auto pResultSeq
= m_aResultSeq
.getArray();
399 const OUString aExtensionValue
= xAttribs
->getValueByName( g_aExtensionAttr
);
400 if ( aExtensionValue
.isEmpty() )
401 throw css::xml::sax::SAXException(); // TODO: the Extension value must present
403 const OUString aContentTypeValue
= xAttribs
->getValueByName( g_aContentTypeAttr
);
404 if ( aContentTypeValue
.isEmpty() )
405 throw css::xml::sax::SAXException(); // TODO: the ContentType value must present
407 const sal_Int32 nNewResultLen
= m_aResultSeq
[0].getLength() + 1;
408 pResultSeq
[0].realloc( nNewResultLen
);
409 auto pSeq
= pResultSeq
[0].getArray();
411 pSeq
[nNewResultLen
-1].First
= aExtensionValue
;
412 pSeq
[nNewResultLen
-1].Second
= aContentTypeValue
;
414 else if ( aName
== g_aOverrideElement
)
416 sal_Int32 nNewLength
= m_aElementsSeq
.size() + 1;
417 if ( nNewLength
!= 2 )
418 throw css::xml::sax::SAXException(); // TODO: this element must be the second level element
420 m_aElementsSeq
.push_back( aName
);
422 if ( !m_aResultSeq
.hasElements() )
423 m_aResultSeq
.realloc( 2 );
425 if ( m_aResultSeq
.getLength() != 2 )
426 throw uno::RuntimeException("m_aResultSeq already has elements and is not reallocated to 2.");
428 auto pResultSeq
= m_aResultSeq
.getArray();
430 OUString aPartNameValue
= xAttribs
->getValueByName( g_aPartNameAttr
);
431 if ( aPartNameValue
.isEmpty() )
432 throw css::xml::sax::SAXException(); // TODO: the PartName value must present
434 OUString aContentTypeValue
= xAttribs
->getValueByName( g_aContentTypeAttr
);
435 if ( aContentTypeValue
.isEmpty() )
436 throw css::xml::sax::SAXException(); // TODO: the ContentType value must present
438 sal_Int32 nNewResultLen
= m_aResultSeq
[1].getLength() + 1;
439 pResultSeq
[1].realloc( nNewResultLen
);
440 auto pSeq
= pResultSeq
[1].getArray();
442 pSeq
[nNewResultLen
-1].First
= aPartNameValue
;
443 pSeq
[nNewResultLen
-1].Second
= aContentTypeValue
;
446 throw css::xml::sax::SAXException(); // TODO: no other elements expected!
449 throw css::xml::sax::SAXException(); // TODO: no other elements expected!
453 void SAL_CALL
OFOPXMLHelper_Impl::endElement( const OUString
& aName
)
455 if ( m_nFormat
== RELATIONINFO_FORMAT
|| m_nFormat
== CONTENTTYPE_FORMAT
)
457 sal_Int32 nLength
= m_aElementsSeq
.size();
459 throw css::xml::sax::SAXException(); // TODO: no other end elements expected!
461 if ( m_aElementsSeq
[nLength
-1] != aName
)
462 throw css::xml::sax::SAXException(); // TODO: unexpected element ended
464 m_aElementsSeq
.resize( nLength
- 1 );
469 void SAL_CALL
OFOPXMLHelper_Impl::characters( const OUString
& /*aChars*/ )
474 void SAL_CALL
OFOPXMLHelper_Impl::ignorableWhitespace( const OUString
& /*aWhitespaces*/ )
479 void SAL_CALL
OFOPXMLHelper_Impl::processingInstruction( const OUString
& /*aTarget*/, const OUString
& /*aData*/ )
484 void SAL_CALL
OFOPXMLHelper_Impl::setDocumentLocator( const uno::Reference
< css::xml::sax::XLocator
>& /*xLocator*/ )
488 } // namespace comphelper
490 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */