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 .
20 #include <oox/vml/vmlinputstream.hxx>
22 #include <com/sun/star/io/IOException.hpp>
23 #include <com/sun/star/io/XTextInputStream2.hpp>
26 #include <rtl/strbuf.hxx>
27 #include <osl/diagnose.h>
28 #include <oox/helper/textinputstream.hxx>
32 using namespace ::com::sun::star::io
;
33 using namespace ::com::sun::star::uno
;
37 const char* lclFindCharacter( const char* pcBeg
, const char* pcEnd
, char cChar
)
39 sal_Int32 nIndex
= rtl_str_indexOfChar_WithLength( pcBeg
, static_cast< sal_Int32
>( pcEnd
- pcBeg
), cChar
);
40 return (nIndex
< 0) ? pcEnd
: (pcBeg
+ nIndex
);
43 bool lclIsWhiteSpace( char cChar
)
45 return cChar
>= 0 && cChar
<= 32;
48 const char* lclFindWhiteSpace( const char* pcBeg
, const char* pcEnd
)
50 for( ; pcBeg
< pcEnd
; ++pcBeg
)
51 if( lclIsWhiteSpace( *pcBeg
) )
56 const char* lclFindNonWhiteSpace( const char* pcBeg
, const char* pcEnd
)
58 for( ; pcBeg
< pcEnd
; ++pcBeg
)
59 if( !lclIsWhiteSpace( *pcBeg
) )
64 const char* lclTrimWhiteSpaceFromEnd( const char* pcBeg
, const char* pcEnd
)
66 while( (pcBeg
< pcEnd
) && lclIsWhiteSpace( pcEnd
[ -1 ] ) )
71 void lclAppendToBuffer( OStringBuffer
& rBuffer
, const char* pcBeg
, const char* pcEnd
)
73 rBuffer
.append( pcBeg
, static_cast< sal_Int32
>( pcEnd
- pcBeg
) );
76 void lclProcessAttribs( OStringBuffer
& rBuffer
, const char* pcBeg
, const char* pcEnd
)
78 /* Map attribute names to char-pointer of all attributes. This map is used
79 to find multiple occurrences of attributes with the same name. The
80 mapped pointers are used as map key in the next map below. */
81 typedef ::std::map
< OString
, const char* > AttributeNameMap
;
82 AttributeNameMap aAttributeNames
;
84 /* Map the char-pointers of all attributes to the full attribute definition
85 string. This preserves the original order of the used attributes. */
86 typedef ::std::map
< const char*, OString
> AttributeDataMap
;
87 AttributeDataMap aAttributes
;
90 const char* pcNameBeg
= pcBeg
;
91 while( bOk
&& (pcNameBeg
< pcEnd
) )
93 // pcNameBeg points to begin of attribute name, find equality sign
94 const char* pcEqualSign
= lclFindCharacter( pcNameBeg
, pcEnd
, '=' );
95 bOk
= (pcEqualSign
< pcEnd
);
98 // find end of attribute name (ignore whitespace between name and equality sign)
99 const char* pcNameEnd
= lclTrimWhiteSpaceFromEnd( pcNameBeg
, pcEqualSign
);
100 bOk
= (pcNameBeg
< pcNameEnd
);
103 // find begin of attribute value (must be single or double quote)
104 const char* pcValueBeg
= lclFindNonWhiteSpace( pcEqualSign
+ 1, pcEnd
);
105 bOk
= (pcValueBeg
< pcEnd
) && ((*pcValueBeg
== '\'') || (*pcValueBeg
== '"'));
108 // find end of attribute value (matching quote character)
109 const char* pcValueEnd
= lclFindCharacter( pcValueBeg
+ 1, pcEnd
, *pcValueBeg
);
110 bOk
= (pcValueEnd
< pcEnd
);
114 OString
aAttribName( pcNameBeg
, static_cast< sal_Int32
>( pcNameEnd
- pcNameBeg
) );
115 OString
aAttribData( pcNameBeg
, static_cast< sal_Int32
>( pcValueEnd
- pcNameBeg
) );
116 // search for an existing attribute with the same name
117 AttributeNameMap::iterator aIt
= aAttributeNames
.find( aAttribName
);
118 // remove its definition from the data map
119 if( aIt
!= aAttributeNames
.end() )
120 aAttributes
.erase( aIt
->second
);
121 // insert the attribute into both maps
122 aAttributeNames
[ aAttribName
] = pcNameBeg
;
123 aAttributes
[ pcNameBeg
] = aAttribData
;
124 // continue with next attribute (skip whitespace after this attribute)
125 pcNameBeg
= pcValueEnd
;
126 if( pcNameBeg
< pcEnd
)
128 bOk
= lclIsWhiteSpace( *pcNameBeg
);
130 pcNameBeg
= lclFindNonWhiteSpace( pcNameBeg
+ 1, pcEnd
);
138 // if no error has occurred, build the resulting attribute list
140 for (auto const& attrib
: aAttributes
)
141 rBuffer
.append( " " + attrib
.second
);
142 // on error, just append the complete passed string
144 lclAppendToBuffer( rBuffer
, pcBeg
, pcEnd
);
147 void lclProcessElement( OStringBuffer
& rBuffer
, const OString
& rElement
)
149 // check that passed string starts and ends with the brackets of an XML element
150 sal_Int32 nElementLen
= rElement
.getLength();
151 if( nElementLen
== 0 )
154 const char* pcOpen
= rElement
.getStr();
155 const char* pcClose
= pcOpen
+ nElementLen
- 1;
157 // no complete element found
158 if( (pcOpen
>= pcClose
) || (*pcOpen
!= '<') || (*pcClose
!= '>') )
160 // just append all passed characters
161 rBuffer
.append( rElement
);
164 // skip parser instructions: '<![...]>'
165 else if( (nElementLen
>= 5) && (pcOpen
[ 1 ] == '!') && (pcOpen
[ 2 ] == '[') && (pcClose
[ -1 ] == ']') )
170 // just append any xml prolog (text directive) or processing instructions: <?...?>
171 else if( (nElementLen
>= 4) && (pcOpen
[ 1 ] == '?') && (pcClose
[ -1 ] == '?') )
173 rBuffer
.append( rElement
);
176 // replace '<br>' element with newline
177 else if( (nElementLen
>= 4) && (pcOpen
[ 1 ] == 'b') && (pcOpen
[ 2 ] == 'r') && (lclFindNonWhiteSpace( pcOpen
+ 3, pcClose
) == pcClose
) )
179 rBuffer
.append( '\n' );
182 // check start elements and simple elements for repeated attributes
183 else if( pcOpen
[ 1 ] != '/' )
185 // find positions of text content inside brackets, exclude '/' in '<simpleelement/>'
186 const char* pcContentBeg
= pcOpen
+ 1;
187 bool bIsEmptyElement
= pcClose
[ -1 ] == '/';
188 const char* pcContentEnd
= bIsEmptyElement
? (pcClose
- 1) : pcClose
;
189 // append opening bracket and element name to buffer
190 const char* pcWhiteSpace
= lclFindWhiteSpace( pcContentBeg
, pcContentEnd
);
191 lclAppendToBuffer( rBuffer
, pcOpen
, pcWhiteSpace
);
192 // find begin of attributes, and process all attributes
193 const char* pcAttribBeg
= lclFindNonWhiteSpace( pcWhiteSpace
, pcContentEnd
);
194 if( pcAttribBeg
< pcContentEnd
)
195 lclProcessAttribs( rBuffer
, pcAttribBeg
, pcContentEnd
);
197 if( bIsEmptyElement
)
198 rBuffer
.append( '/' );
199 rBuffer
.append( '>' );
202 // append end elements without further processing
205 rBuffer
.append( rElement
);
209 bool lclProcessCharacters( OStringBuffer
& rBuffer
, const OString
& rChars
)
211 /* MSO has a very weird way to store and handle whitespaces. The stream
212 may contain lots of spaces, tabs, and newlines which have to be handled
213 as single space character. This will be done in this function.
215 If the element text contains a literal line break, it will be stored as
216 <br> tag (without matching </br> element). This input stream wrapper
217 will replace this element with a literal LF character (see below).
219 A single space character for its own is stored as is. Example: The
222 represents a single space character. The XML parser will ignore this
223 space character completely without issuing a 'characters' event. The
224 VML import filter implementation has to react on this case manually.
226 A single space character following another character is stored
227 literally and must not be stripped away here. Example: The element
229 contains the three letters a, b, and c, followed by a space character.
231 Consecutive space characters, or a leading single space character, are
232 stored in a <span> element. If there are N space characters (N > 1),
233 then the <span> element contains exactly (N-1) NBSP (non-breaking
234 space) characters, followed by a regular space character. Examples:
236 <font><span style='mso-spacerun:yes'>\xA0\xA0\xA0 </span></font>
237 represents 4 consecutive space characters. Has to be handled by the
238 implementation. The element
239 <font><span style='mso-spacerun:yes'> abc</span></font>
240 represents a space characters followed by the letters a, b, c. These
241 strings have to be handled by the VML import filter implementation.
244 // passed string ends with the leading opening bracket of an XML element
245 const char* pcBeg
= rChars
.getStr();
246 const char* pcEnd
= pcBeg
+ rChars
.getLength();
247 bool bHasBracket
= (pcBeg
< pcEnd
) && (pcEnd
[ -1 ] == '<');
248 if( bHasBracket
) --pcEnd
;
250 // skip leading whitespace
251 const char* pcContentsBeg
= lclFindNonWhiteSpace( pcBeg
, pcEnd
);
252 while( pcContentsBeg
< pcEnd
)
254 const char* pcWhitespaceBeg
= lclFindWhiteSpace( pcContentsBeg
+ 1, pcEnd
);
255 lclAppendToBuffer( rBuffer
, pcContentsBeg
, pcWhitespaceBeg
);
256 if( pcWhitespaceBeg
< pcEnd
)
257 rBuffer
.append( ' ' );
258 pcContentsBeg
= lclFindNonWhiteSpace( pcWhitespaceBeg
, pcEnd
);
266 constexpr OStringLiteral
gaOpeningCData( "<![CDATA[" );
267 constexpr OStringLiteral
gaClosingCData( "]]>" );
269 InputStream::InputStream( const Reference
< XComponentContext
>& rxContext
, const Reference
< XInputStream
>& rxInStrm
) :
270 // use single-byte ISO-8859-1 encoding which maps all byte characters to the first 256 Unicode characters
271 mxTextStrm( TextInputStream::createXTextInputStream( rxContext
, rxInStrm
, RTL_TEXTENCODING_ISO_8859_1
) ),
272 maOpeningBracket
{ '<' },
273 maClosingBracket
{ '>' },
276 if (!mxTextStrm
.is())
280 InputStream::~InputStream()
284 sal_Int32 SAL_CALL
InputStream::readBytes( Sequence
< sal_Int8
>& rData
, sal_Int32 nBytesToRead
)
286 if( nBytesToRead
< 0 )
289 rData
.realloc( nBytesToRead
);
290 sal_Int8
* pcDest
= rData
.getArray();
292 while( (nBytesToRead
> 0) && !mxTextStrm
->isEOF() )
295 sal_Int32 nReadSize
= ::std::min( nBytesToRead
, maBuffer
.getLength() - mnBufferPos
);
298 memcpy( pcDest
+ nRet
, maBuffer
.getStr() + mnBufferPos
, static_cast< size_t >( nReadSize
) );
299 mnBufferPos
+= nReadSize
;
300 nBytesToRead
-= nReadSize
;
304 if( nRet
< rData
.getLength() )
305 rData
.realloc( nRet
);
309 sal_Int32 SAL_CALL
InputStream::readSomeBytes( Sequence
< sal_Int8
>& rData
, sal_Int32 nMaxBytesToRead
)
311 return readBytes( rData
, nMaxBytesToRead
);
314 void SAL_CALL
InputStream::skipBytes( sal_Int32 nBytesToSkip
)
316 if( nBytesToSkip
< 0 )
319 while( (nBytesToSkip
> 0) && !mxTextStrm
->isEOF() )
322 sal_Int32 nSkipSize
= ::std::min( nBytesToSkip
, maBuffer
.getLength() - mnBufferPos
);
323 mnBufferPos
+= nSkipSize
;
324 nBytesToSkip
-= nSkipSize
;
328 sal_Int32 SAL_CALL
InputStream::available()
331 return maBuffer
.getLength() - mnBufferPos
;
334 void SAL_CALL
InputStream::closeInput()
336 mxTextStrm
->closeInput();
339 // private --------------------------------------------------------------------
341 void InputStream::updateBuffer()
343 while( (mnBufferPos
>= maBuffer
.getLength()) && !mxTextStrm
->isEOF() )
345 // collect new contents in a string buffer
346 OStringBuffer aBuffer
;
348 // read and process characters until the opening bracket of the next XML element
349 OString aChars
= readToElementBegin();
350 bool bHasOpeningBracket
= lclProcessCharacters( aBuffer
, aChars
);
352 // read and process characters until (and including) closing bracket (an XML element)
353 OSL_ENSURE( bHasOpeningBracket
|| mxTextStrm
->isEOF(), "InputStream::updateBuffer - missing opening bracket of XML element" );
354 if( bHasOpeningBracket
&& !mxTextStrm
->isEOF() )
356 // read the element text (add the leading opening bracket manually)
357 OString aElement
= "<" + readToElementEnd();
358 // check for CDATA part, starting with '<![CDATA['
359 if( aElement
.match( gaOpeningCData
) )
361 // search the end tag ']]>'
362 while( ((aElement
.getLength() < gaClosingCData
.getLength()) || !aElement
.endsWith( gaClosingCData
)) && !mxTextStrm
->isEOF() )
363 aElement
+= readToElementEnd();
364 // copy the entire CDATA part
365 aBuffer
.append( aElement
);
369 // no CDATA part - process the contents of the element
370 lclProcessElement( aBuffer
, aElement
);
374 maBuffer
= aBuffer
.makeStringAndClear();
379 OString
InputStream::readToElementBegin()
381 return OUStringToOString( mxTextStrm
->readString( maOpeningBracket
, false ), RTL_TEXTENCODING_ISO_8859_1
);
384 OString
InputStream::readToElementEnd()
386 OString aText
= OUStringToOString( mxTextStrm
->readString( maClosingBracket
, false ), RTL_TEXTENCODING_ISO_8859_1
);
387 OSL_ENSURE( aText
.endsWith(">"), "InputStream::readToElementEnd - missing closing bracket of XML element" );
391 } // namespace oox::vml
393 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */