1
/* This file is part of the KDE project
2
Copyright (C) 2004 David Faure <faure@kde.org>
3
Copyright (C) 2007 Thomas Zander <zander@kde.org>
5
This library is free software; you can redistribute it and/or
6
modify it under the terms of the GNU Library General Public
7
License as published by the Free Software Foundation; either
8
version 2 of the License, or (at your option) any later version.
10
This library is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
Library General Public License for more details.
15
You should have received a copy of the GNU Library General Public License
16
along with this library; see the file COPYING.LIB. If not, write to
17
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18
* Boston, MA 02110-1301, USA.
21
#include "KoXmlWriter.h"
28
static const int s_indentBufferLength = 100;
29
static const int s_escapeBufferLen = 10000;
31
class KoXmlWriter::Private
34
Private(QIODevice* dev_, int indentLevel = 0) : dev(dev_), baseIndentLevel(indentLevel) {}
36
delete[] indentBuffer;
37
delete[] escapeBuffer;
38
//TODO: look at if we must delete "dev". For me we must delete it otherwise we will leak it
45
char* indentBuffer; // maybe make it static, but then it needs a K_GLOBAL_STATIC
46
// and would eat 1K all the time... Maybe refcount it :)
47
char* escapeBuffer; // can't really be static if we want to be thread-safe
50
KoXmlWriter::KoXmlWriter(QIODevice* dev, int indentLevel)
51
: d(new Private(dev, indentLevel))
56
void KoXmlWriter::init()
58
d->indentBuffer = new char[ s_indentBufferLength ];
59
memset(d->indentBuffer, ' ', s_indentBufferLength);
60
*d->indentBuffer = '\n'; // write newline before indentation, in one go
62
d->escapeBuffer = new char[s_escapeBufferLen];
63
if (!d->dev->isOpen())
64
d->dev->open(QIODevice::WriteOnly);
67
KoXmlWriter::~KoXmlWriter()
72
void KoXmlWriter::startDocument(const char* rootElemName, const char* publicId, const char* systemId)
74
Q_ASSERT(d->tags.isEmpty());
75
writeCString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
76
// There isn't much point in a doctype if there's no DTD to refer to
77
// (I'm told that files that are validated by a RelaxNG schema cannot refer to the schema)
79
writeCString("<!DOCTYPE ");
80
writeCString(rootElemName);
81
writeCString(" PUBLIC \"");
82
writeCString(publicId);
83
writeCString("\" \"");
84
writeCString(systemId);
90
void KoXmlWriter::endDocument()
92
// just to do exactly like QDom does (newline at end of file).
94
Q_ASSERT(d->tags.isEmpty());
97
// returns the value of indentInside of the parent
98
bool KoXmlWriter::prepareForChild()
100
if (!d->tags.isEmpty()) {
101
Tag& parent = d->tags.top();
102
if (!parent.hasChildren) {
103
closeStartElement(parent);
104
parent.hasChildren = true;
105
parent.lastChildIsText = false;
107
if (parent.indentInside) {
110
return parent.indentInside;
115
void KoXmlWriter::prepareForTextNode()
117
if (d->tags.isEmpty())
119
Tag& parent = d->tags.top();
120
if (!parent.hasChildren) {
121
closeStartElement(parent);
122
parent.hasChildren = true;
123
parent.lastChildIsText = true;
127
void KoXmlWriter::startElement(const char* tagName, bool indentInside)
129
Q_ASSERT(tagName != 0);
131
// Tell parent that it has children
132
bool parentIndent = prepareForChild();
134
d->tags.push(Tag(tagName, parentIndent && indentInside));
136
writeCString(tagName);
137
//kDebug(s_area) << tagName;
140
void KoXmlWriter::addCompleteElement(const char* cstr)
147
void KoXmlWriter::addCompleteElement(QIODevice* indev)
150
bool openOk = indev->open(QIODevice::ReadOnly);
154
static const int MAX_CHUNK_SIZE = 8 * 1024; // 8 KB
156
buffer.resize(MAX_CHUNK_SIZE);
157
while (!indev->atEnd()) {
158
qint64 len = indev->read(buffer.data(), buffer.size());
159
if (len <= 0) // e.g. on error
161
d->dev->write(buffer.data(), len);
165
void KoXmlWriter::endElement()
167
if (d->tags.isEmpty())
168
kWarning() << "Ouch, endElement() was called more times than startElement(). "
169
"The generated XML will be invalid! "
170
"Please report this bug (by saving the document to another format...)" << endl;
172
Tag tag = d->tags.pop();
173
//kDebug(s_area) <<" tagName=" << tag.tagName <<" hasChildren=" << tag.hasChildren;
174
if (!tag.hasChildren) {
177
if (tag.indentInside && !tag.lastChildIsText) {
181
Q_ASSERT(tag.tagName != 0);
182
writeCString(tag.tagName);
187
void KoXmlWriter::addTextNode(const QByteArray& cstr)
189
// Same as the const char* version below, but here we know the size
190
prepareForTextNode();
191
char* escaped = escapeForXML(cstr.constData(), cstr.size());
192
writeCString(escaped);
193
if (escaped != d->escapeBuffer)
197
void KoXmlWriter::addTextNode(const char* cstr)
199
prepareForTextNode();
200
char* escaped = escapeForXML(cstr, -1);
201
writeCString(escaped);
202
if (escaped != d->escapeBuffer)
206
void KoXmlWriter::addProcessingInstruction(const char* cstr)
208
prepareForTextNode();
214
void KoXmlWriter::addAttribute(const char* attrName, const QByteArray& value)
216
// Same as the const char* one, but here we know the size
218
writeCString(attrName);
220
char* escaped = escapeForXML(value.constData(), value.size());
221
writeCString(escaped);
222
if (escaped != d->escapeBuffer)
227
void KoXmlWriter::addAttribute(const char* attrName, const char* value)
230
writeCString(attrName);
232
char* escaped = escapeForXML(value, -1);
233
writeCString(escaped);
234
if (escaped != d->escapeBuffer)
239
void KoXmlWriter::addAttribute(const char* attrName, double value)
242
str.setNum(value, 'f', 11);
243
addAttribute(attrName, str.data());
246
void KoXmlWriter::addAttribute(const char* attrName, float value)
249
str.setNum(value, 'f', FLT_DIG);
250
addAttribute(attrName, str.data());
253
void KoXmlWriter::addAttributePt(const char* attrName, double value)
256
str.setNum(value, 'f', 11);
258
addAttribute(attrName, str.data());
261
void KoXmlWriter::addAttributePt(const char* attrName, float value)
264
str.setNum(value, 'f', FLT_DIG);
266
addAttribute(attrName, str.data());
269
void KoXmlWriter::writeIndent()
271
// +1 because of the leading '\n'
272
d->dev->write(d->indentBuffer, qMin(indentLevel() + 1,
273
s_indentBufferLength));
276
void KoXmlWriter::writeString(const QString& str)
278
// cachegrind says .utf8() is where most of the time is spent
279
const QByteArray cstr = str.toUtf8();
283
// In case of a reallocation (ret value != d->buffer), the caller owns the return value,
284
// it must delete it (with [])
285
char* KoXmlWriter::escapeForXML(const char* source, int length = -1) const
287
// we're going to be pessimistic on char length; so lets make the outputLength less
288
// the amount one char can take: 6
289
char* destBoundary = d->escapeBuffer + s_escapeBufferLen - 6;
290
char* destination = d->escapeBuffer;
291
char* output = d->escapeBuffer;
292
const char* src = source; // src moves, source remains
294
if (destination >= destBoundary) {
295
// When we come to realize that our escaped string is going to
296
// be bigger than the escape buffer (this shouldn't happen very often...),
297
// we drop the idea of using it, and we allocate a bigger buffer.
298
// Note that this if() can only be hit once per call to the method.
300
length = qstrlen(source); // expensive...
301
uint newLength = length * 6 + 1; // worst case. 6 is due to " and '
302
char* buffer = new char[ newLength ];
303
destBoundary = buffer + newLength;
304
uint amountOfCharsAlreadyCopied = destination - d->escapeBuffer;
305
memcpy(buffer, d->escapeBuffer, amountOfCharsAlreadyCopied);
307
destination = buffer + amountOfCharsAlreadyCopied;
311
memcpy(destination, "<", 4);
315
memcpy(destination, ">", 4);
319
memcpy(destination, """, 6);
324
memcpy(destination, "'", 6);
329
memcpy(destination, "&", 5);
336
*destination++ = *src++;
341
// NOTREACHED (see case 0)
345
void KoXmlWriter::addManifestEntry(const QString& fullPath, const QString& mediaType)
347
startElement("manifest:file-entry");
348
addAttribute("manifest:media-type", mediaType);
349
addAttribute("manifest:full-path", fullPath);
353
void KoXmlWriter::addConfigItem(const QString & configName, const QString& value)
355
startElement("config:config-item");
356
addAttribute("config:name", configName);
357
addAttribute("config:type", "string");
362
void KoXmlWriter::addConfigItem(const QString & configName, bool value)
364
startElement("config:config-item");
365
addAttribute("config:name", configName);
366
addAttribute("config:type", "boolean");
367
addTextNode(value ? "true" : "false");
371
void KoXmlWriter::addConfigItem(const QString & configName, int value)
373
startElement("config:config-item");
374
addAttribute("config:name", configName);
375
addAttribute("config:type", "int");
376
addTextNode(QString::number(value));
380
void KoXmlWriter::addConfigItem(const QString & configName, double value)
382
startElement("config:config-item");
383
addAttribute("config:name", configName);
384
addAttribute("config:type", "double");
385
addTextNode(QString::number(value));
389
void KoXmlWriter::addConfigItem(const QString & configName, float value)
391
startElement("config:config-item");
392
addAttribute("config:name", configName);
393
addAttribute("config:type", "double");
394
addTextNode(QString::number(value));
398
void KoXmlWriter::addConfigItem(const QString & configName, long value)
400
startElement("config:config-item");
401
addAttribute("config:name", configName);
402
addAttribute("config:type", "long");
403
addTextNode(QString::number(value));
407
void KoXmlWriter::addConfigItem(const QString & configName, short value)
409
startElement("config:config-item");
410
addAttribute("config:name", configName);
411
addAttribute("config:type", "short");
412
addTextNode(QString::number(value));
416
void KoXmlWriter::addTextSpan(const QString& text)
418
QMap<int, int> tabCache;
419
addTextSpan(text, tabCache);
422
void KoXmlWriter::addTextSpan(const QString& text, const QMap<int, int>& tabCache)
424
int len = text.length();
425
int nrSpaces = 0; // number of consecutive spaces
426
bool leadingSpace = false;
430
// Accumulate chars either in str or in nrSpaces (for spaces).
431
// Flush str when writing a subelement (for spaces or for another reason)
432
// Flush nrSpaces when encountering two or more consecutive spaces
433
for (int i = 0; i < len ; ++i) {
437
// For the first space we use ' '.
438
// "it is good practice to use (text:s) for the second and all following SPACE
439
// characters in a sequence." (per the ODF spec)
440
// however, per the HTML spec, "authors should not rely on user agents to render
441
// white space immediately after a start tag or immediately before an end tag"
442
// (and both we and OO.o ignore leading spaces in <text:p> or <text:h> elements...)
447
if (nrSpaces > 0) { // there are more spaces
451
startElement("text:s");
452
if (nrSpaces > 1) // it's 1 by default
453
addAttribute("text:c", nrSpaces);
458
leadingSpace = false;
460
switch (ch.unicode()) {
465
startElement("text:tab");
466
if (tabCache.contains(i))
467
addAttribute("text:tab-ref", tabCache[i] + 1);
474
startElement("text:line-break");
487
// either we still have text in str or we have spaces in nrSpaces
488
if (!str.isEmpty()) {
491
if (nrSpaces > 0) { // there are more spaces
492
startElement("text:s");
493
if (nrSpaces > 1) // it's 1 by default
494
addAttribute("text:c", nrSpaces);
499
QIODevice *KoXmlWriter::device() const
504
int KoXmlWriter::indentLevel() const
506
return d->tags.size() + d->baseIndentLevel;
509
QList<const char*> KoXmlWriter::tagHierarchy() const
511
QList<const char*> answer;
512
foreach(const Tag & tag, d->tags)
513
answer.append(tag.tagName);