lib

KoXmlWriter.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2004 David Faure <faure@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20#include "KoXmlWriter.h"
21
22#include <tdeglobal.h> // kMin
23#include <kdebug.h>
24#include <tqiodevice.h>
25#include <float.h>
26
27static const int s_indentBufferLength = 100;
28
30 : m_dev( dev ), m_baseIndentLevel( indentLevel )
31{
32 init();
33}
34
35void KoXmlWriter::init()
36{
37 m_indentBuffer = new char[ s_indentBufferLength ];
38 memset( m_indentBuffer, ' ', s_indentBufferLength );
39 *m_indentBuffer = '\n'; // write newline before indentation, in one go
40
41 m_escapeBuffer = new char[s_escapeBufferLen];
42}
43
45{
46 delete[] m_indentBuffer;
47 delete[] m_escapeBuffer;
48}
49
50void KoXmlWriter::startDocument( const char* rootElemName, const char* publicId, const char* systemId )
51{
52 Q_ASSERT( m_tags.isEmpty() );
53 writeCString( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
54 // There isn't much point in a doctype if there's no DTD to refer to
55 // (I'm told that files that are validated by a RelaxNG schema cannot refer to the schema)
56 if ( publicId ) {
57 writeCString( "<!DOCTYPE " );
58 writeCString( rootElemName );
59 writeCString( " PUBLIC \"" );
60 writeCString( publicId );
61 writeCString( "\" \"" );
62 writeCString( systemId );
63 writeCString( "\"" );
64 writeCString( ">\n" );
65 }
66}
67
69{
70 // just to do exactly like TQDom does (newline at end of file).
71 writeChar( '\n' );
72 Q_ASSERT( m_tags.isEmpty() );
73}
74
75// returns the value of indentInside of the parent
76bool KoXmlWriter::prepareForChild()
77{
78 if ( !m_tags.isEmpty() ) {
79 Tag& parent = m_tags.top();
80 if ( !parent.hasChildren ) {
81 closeStartElement( parent );
82 parent.hasChildren = true;
83 parent.lastChildIsText = false;
84 }
85 if ( parent.indentInside ) {
86 writeIndent();
87 }
88 return parent.indentInside;
89 }
90 return true;
91}
92
93void KoXmlWriter::prepareForTextNode()
94{
95 Tag& parent = m_tags.top();
96 if ( !parent.hasChildren ) {
97 closeStartElement( parent );
98 parent.hasChildren = true;
99 parent.lastChildIsText = true;
100 }
101}
102
103void KoXmlWriter::startElement( const char* tagName, bool indentInside )
104{
105 Q_ASSERT( tagName != 0 );
106
107 // Tell parent that it has children
108 bool parentIndent = prepareForChild();
109
110 m_tags.push( Tag( tagName, parentIndent && indentInside ) );
111 writeChar( '<' );
112 writeCString( tagName );
113 //kdDebug() << k_funcinfo << tagName << endl;
114}
115
116void KoXmlWriter::addCompleteElement( const char* cstr )
117{
118 prepareForChild();
119 writeCString( cstr );
120}
121
122
123void KoXmlWriter::addCompleteElement( TQIODevice* indev )
124{
125 prepareForChild();
126 bool openOk = indev->open( IO_ReadOnly );
127 Q_ASSERT( openOk );
128 if ( !openOk )
129 return;
130 static const int MAX_CHUNK_SIZE = 8*1024; // 8 KB
131 TQByteArray buffer(MAX_CHUNK_SIZE);
132 while ( !indev->atEnd() ) {
133 TQ_LONG len = indev->readBlock( buffer.data(), buffer.size() );
134 if ( len <= 0 ) // e.g. on error
135 break;
136 m_dev->writeBlock( buffer.data(), len );
137 }
138}
139
141{
142 if ( m_tags.isEmpty() )
143 kdWarning() << "Ouch, endElement() was called more times than startElement(). "
144 "The generated XML will be invalid! "
145 "Please report this bug (by saving the document to another format...)" << endl;
146
147 Tag tag = m_tags.pop();
148 //kdDebug() << k_funcinfo << " tagName=" << tag.tagName << " hasChildren=" << tag.hasChildren << endl;
149 if ( !tag.hasChildren ) {
150 writeCString( "/>" );
151 }
152 else {
153 if ( tag.indentInside && !tag.lastChildIsText ) {
154 writeIndent();
155 }
156 writeCString( "</" );
157 Q_ASSERT( tag.tagName != 0 );
158 writeCString( tag.tagName );
159 writeChar( '>' );
160 }
161}
162
163void KoXmlWriter::addTextNode( const char* cstr )
164{
165 prepareForTextNode();
166 char* escaped = escapeForXML( cstr, -1 );
167 writeCString( escaped );
168 if(escaped != m_escapeBuffer)
169 delete[] escaped;
170}
171
173{
174 prepareForTextNode();
175 writeCString( "<?" );
176 addTextNode( cstr );
177 writeCString( "?>");
178}
179
180void KoXmlWriter::addAttribute( const char* attrName, const char* value )
181{
182 writeChar( ' ' );
183 writeCString( attrName );
184 writeCString("=\"");
185 char* escaped = escapeForXML( value, -1 );
186 writeCString( escaped );
187 if(escaped != m_escapeBuffer)
188 delete[] escaped;
189 writeChar( '"' );
190}
191
192void KoXmlWriter::addAttribute( const char* attrName, double value )
193{
194 TQCString str;
195 str.setNum( value, 'g', DBL_DIG );
196 addAttribute( attrName, str.data() );
197}
198
199void KoXmlWriter::addAttributePt( const char* attrName, double value )
200{
201 TQCString str;
202 str.setNum( value, 'g', DBL_DIG );
203 str += "pt";
204 addAttribute( attrName, str.data() );
205}
206
207void KoXmlWriter::writeIndent()
208{
209 // +1 because of the leading '\n'
210 m_dev->writeBlock( m_indentBuffer, kMin( indentLevel() + 1,
211 s_indentBufferLength ) );
212}
213
214void KoXmlWriter::writeString( const TQString& str )
215{
216 // cachegrind says .utf8() is where most of the time is spent
217 TQCString cstr = str.utf8();
218 m_dev->writeBlock( cstr.data(), cstr.size() - 1 );
219}
220
221// In case of a reallocation (ret value != m_buffer), the caller owns the return value,
222// it must delete it (with [])
223char* KoXmlWriter::escapeForXML( const char* source, int length = -1 ) const
224{
225 // we're going to be pessimistic on char length; so lets make the outputLength less
226 // the amount one char can take: 6
227 char* destBoundary = m_escapeBuffer + s_escapeBufferLen - 6;
228 char* destination = m_escapeBuffer;
229 char* output = m_escapeBuffer;
230 const char* src = source; // src moves, source remains
231 for ( ;; ) {
232 if(destination >= destBoundary) {
233 // When we come to realize that our escaped string is going to
234 // be bigger than the escape buffer (this shouldn't happen very often...),
235 // we drop the idea of using it, and we allocate a bigger buffer.
236 // Note that this if() can only be hit once per call to the method.
237 if ( length == -1 )
238 length = tqstrlen( source ); // expensive...
239 uint newLength = length * 6 + 1; // worst case. 6 is due to &quot; and &apos;
240 char* buffer = new char[ newLength ];
241 destBoundary = buffer + newLength;
242 uint amountOfCharsAlreadyCopied = destination - m_escapeBuffer;
243 memcpy( buffer, m_escapeBuffer, amountOfCharsAlreadyCopied );
244 output = buffer;
245 destination = buffer + amountOfCharsAlreadyCopied;
246 }
247 switch( *src ) {
248 case 60: // <
249 memcpy( destination, "&lt;", 4 );
250 destination += 4;
251 break;
252 case 62: // >
253 memcpy( destination, "&gt;", 4 );
254 destination += 4;
255 break;
256 case 34: // "
257 memcpy( destination, "&quot;", 6 );
258 destination += 6;
259 break;
260#if 0 // needed?
261 case 39: // '
262 memcpy( destination, "&apos;", 6 );
263 destination += 6;
264 break;
265#endif
266 case 38: // &
267 memcpy( destination, "&amp;", 5 );
268 destination += 5;
269 break;
270 case 0:
271 *destination = '\0';
272 return output;
273 default:
274 *destination++ = *src++;
275 continue;
276 }
277 ++src;
278 }
279 // NOTREACHED (see case 0)
280 return output;
281}
282
283void KoXmlWriter::addManifestEntry( const TQString& fullPath, const TQString& mediaType )
284{
285 startElement( "manifest:file-entry" );
286 addAttribute( "manifest:media-type", mediaType );
287 addAttribute( "manifest:full-path", fullPath );
288 endElement();
289}
290
291void KoXmlWriter::addConfigItem( const TQString & configName, const TQString& value )
292{
293 startElement( "config:config-item" );
294 addAttribute( "config:name", configName );
295 addAttribute( "config:type", "string" );
296 addTextNode( value );
297 endElement();
298}
299
300void KoXmlWriter::addConfigItem( const TQString & configName, bool value )
301{
302 startElement( "config:config-item" );
303 addAttribute( "config:name", configName );
304 addAttribute( "config:type", "boolean" );
305 addTextNode( value ? "true" : "false" );
306 endElement();
307}
308
309void KoXmlWriter::addConfigItem( const TQString & configName, int value )
310{
311 startElement( "config:config-item" );
312 addAttribute( "config:name", configName );
313 addAttribute( "config:type", "int");
314 addTextNode(TQString::number( value ) );
315 endElement();
316}
317
318void KoXmlWriter::addConfigItem( const TQString & configName, double value )
319{
320 startElement( "config:config-item" );
321 addAttribute( "config:name", configName );
322 addAttribute( "config:type", "double" );
323 addTextNode( TQString::number( value ) );
324 endElement();
325}
326
327void KoXmlWriter::addConfigItem( const TQString & configName, long value )
328{
329 startElement( "config:config-item" );
330 addAttribute( "config:name", configName );
331 addAttribute( "config:type", "long" );
332 addTextNode( TQString::number( value ) );
333 endElement();
334}
335
336void KoXmlWriter::addConfigItem( const TQString & configName, short value )
337{
338 startElement( "config:config-item" );
339 addAttribute( "config:name", configName );
340 addAttribute( "config:type", "short" );
341 addTextNode( TQString::number( value ) );
342 endElement();
343}
344
345void KoXmlWriter::addTextSpan( const TQString& text )
346{
347 TQMap<int, int> tabCache;
348 addTextSpan( text, tabCache );
349}
350
351void KoXmlWriter::addTextSpan( const TQString& text, const TQMap<int, int>& tabCache )
352{
353 uint len = text.length();
354 int nrSpaces = 0; // number of consecutive spaces
355 bool leadingSpace = false;
356 TQString str;
357 str.reserve( len );
358
359 // Accumulate chars either in str or in nrSpaces (for spaces).
360 // Flush str when writing a subelement (for spaces or for another reason)
361 // Flush nrSpaces when encountering two or more consecutive spaces
362 for ( uint i = 0; i < len ; ++i ) {
363 TQChar ch = text[i];
364 if ( ch != ' ' ) {
365 if ( nrSpaces > 0 ) {
366 // For the first space we use ' '.
367 // "it is good practice to use (text:s) for the second and all following SPACE
368 // characters in a sequence." (per the ODF spec)
369 // however, per the HTML spec, "authors should not rely on user agents to render
370 // white space immediately after a start tag or immediately before an end tag"
371 // (and both we and OO.o ignore leading spaces in <text:p> or <text:h> elements...)
372 if (!leadingSpace)
373 {
374 str += ' ';
375 --nrSpaces;
376 }
377 if ( nrSpaces > 0 ) { // there are more spaces
378 if ( !str.isEmpty() )
379 addTextNode( str );
380 str = TQString();
381 startElement( "text:s" );
382 if ( nrSpaces > 1 ) // it's 1 by default
383 addAttribute( "text:c", nrSpaces );
384 endElement();
385 }
386 }
387 nrSpaces = 0;
388 leadingSpace = false;
389 }
390 switch ( ch.unicode() ) {
391 case '\t':
392 if ( !str.isEmpty() )
393 addTextNode( str );
394 str = TQString();
395 startElement( "text:tab" );
396 if ( tabCache.contains( i ) )
397 addAttribute( "text:tab-ref", tabCache[i] + 1 );
398 endElement();
399 break;
400 case '\n':
401 if ( !str.isEmpty() )
402 addTextNode( str );
403 str = TQString();
404 startElement( "text:line-break" );
405 endElement();
406 break;
407 case ' ':
408 if ( i == 0 )
409 leadingSpace = true;
410 ++nrSpaces;
411 break;
412 default:
413 str += text[i];
414 break;
415 }
416 }
417 // either we still have text in str or we have spaces in nrSpaces
418 if ( !str.isEmpty() ) {
419 addTextNode( str );
420 }
421 if ( nrSpaces > 0 ) { // there are more spaces
422 startElement( "text:s" );
423 if ( nrSpaces > 1 ) // it's 1 by default
424 addAttribute( "text:c", nrSpaces );
425 endElement();
426 }
427}
~KoXmlWriter()
Destructor.
void addConfigItem(const TQString &configName, const TQString &value)
Special helper for writing config item into settings.xml.
void startElement(const char *tagName, bool indentInside=true)
Start a new element, as a child of the current element.
void addTextNode(const TQString &str)
Overloaded version of addTextNode( const char* ), which is a bit slower because it needs to convert s...
int indentLevel() const
void addAttributePt(const char *attrName, double value)
Add an attribute which represents a distance, measured in pt The number is written out with the highe...
void endDocument()
Call this to terminate an XML document.
void addTextSpan(const TQString &text)
Adds a text span as nodes of the current element.
KoXmlWriter(TQIODevice *dev, int indentLevel=0)
Create a KoXmlWriter instance to write out an XML document into the given TQIODevice.
void startDocument(const char *rootElemName, const char *publicId=0, const char *systemId=0)
Start the XML document.
void endElement()
Terminate the current element.
void addCompleteElement(const char *cstr)
This is quite a special-purpose method, not for everyday use.
void addProcessingInstruction(const char *cstr)
Adds a processing instruction.
void addManifestEntry(const TQString &fullPath, const TQString &mediaType)
Special helper for writing "manifest" files This is equivalent to startElement/2*addAttribute/endElem...
void addAttribute(const char *attrName, const TQString &value)
Overloaded version of addAttribute( const char*, const char* ), which is a bit slower because it need...
Definition KoXmlWriter.h:76