~ubuntu-branches/ubuntu/precise/koffice/precise

« back to all changes in this revision

Viewing changes to libs/odf/KoXmlWriter.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jonathan Riddell
  • Date: 2010-09-21 15:36:35 UTC
  • mfrom: (1.4.1 upstream) (60.2.11 maverick)
  • Revision ID: james.westby@ubuntu.com-20100921153635-6tejqkiro2u21ydi
Tags: 1:2.2.2-0ubuntu3
Add kubuntu_03_fix-crash-on-closing-sqlite-connection-2.2.2.diff and
kubuntu_04_support-large-memo-values-for-msaccess-2.2.2.diff as
recommended by upstream http://kexi-
project.org/wiki/wikiview/index.php@Kexi2.2_Patches.html#sqlite_stab
ility

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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>
 
4
 
 
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.
 
9
 
 
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.
 
14
 
 
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.
 
19
*/
 
20
 
 
21
#include "KoXmlWriter.h"
 
22
 
 
23
#include <kdebug.h>
 
24
#include <QIODevice>
 
25
#include <QByteArray>
 
26
#include <float.h>
 
27
 
 
28
static const int s_indentBufferLength = 100;
 
29
static const int s_escapeBufferLen = 10000;
 
30
 
 
31
class KoXmlWriter::Private
 
32
{
 
33
public:
 
34
    Private(QIODevice* dev_, int indentLevel = 0) : dev(dev_), baseIndentLevel(indentLevel) {}
 
35
    ~Private() {
 
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
 
39
    }
 
40
 
 
41
    QIODevice* dev;
 
42
    QStack<Tag> tags;
 
43
    int baseIndentLevel;
 
44
 
 
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
 
48
};
 
49
 
 
50
KoXmlWriter::KoXmlWriter(QIODevice* dev, int indentLevel)
 
51
        : d(new Private(dev, indentLevel))
 
52
{
 
53
    init();
 
54
}
 
55
 
 
56
void KoXmlWriter::init()
 
57
{
 
58
    d->indentBuffer = new char[ s_indentBufferLength ];
 
59
    memset(d->indentBuffer, ' ', s_indentBufferLength);
 
60
    *d->indentBuffer = '\n'; // write newline before indentation, in one go
 
61
 
 
62
    d->escapeBuffer = new char[s_escapeBufferLen];
 
63
    if (!d->dev->isOpen())
 
64
        d->dev->open(QIODevice::WriteOnly);
 
65
}
 
66
 
 
67
KoXmlWriter::~KoXmlWriter()
 
68
{
 
69
    delete d;
 
70
}
 
71
 
 
72
void KoXmlWriter::startDocument(const char* rootElemName, const char* publicId, const char* systemId)
 
73
{
 
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)
 
78
    if (publicId) {
 
79
        writeCString("<!DOCTYPE ");
 
80
        writeCString(rootElemName);
 
81
        writeCString(" PUBLIC \"");
 
82
        writeCString(publicId);
 
83
        writeCString("\" \"");
 
84
        writeCString(systemId);
 
85
        writeCString("\"");
 
86
        writeCString(">\n");
 
87
    }
 
88
}
 
89
 
 
90
void KoXmlWriter::endDocument()
 
91
{
 
92
    // just to do exactly like QDom does (newline at end of file).
 
93
    writeChar('\n');
 
94
    Q_ASSERT(d->tags.isEmpty());
 
95
}
 
96
 
 
97
// returns the value of indentInside of the parent
 
98
bool KoXmlWriter::prepareForChild()
 
99
{
 
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;
 
106
        }
 
107
        if (parent.indentInside) {
 
108
            writeIndent();
 
109
        }
 
110
        return parent.indentInside;
 
111
    }
 
112
    return true;
 
113
}
 
114
 
 
115
void KoXmlWriter::prepareForTextNode()
 
116
{
 
117
    if (d->tags.isEmpty())
 
118
        return;
 
119
    Tag& parent = d->tags.top();
 
120
    if (!parent.hasChildren) {
 
121
        closeStartElement(parent);
 
122
        parent.hasChildren = true;
 
123
        parent.lastChildIsText = true;
 
124
    }
 
125
}
 
126
 
 
127
void KoXmlWriter::startElement(const char* tagName, bool indentInside)
 
128
{
 
129
    Q_ASSERT(tagName != 0);
 
130
 
 
131
    // Tell parent that it has children
 
132
    bool parentIndent = prepareForChild();
 
133
    
 
134
    d->tags.push(Tag(tagName, parentIndent && indentInside));
 
135
    writeChar('<');
 
136
    writeCString(tagName);
 
137
    //kDebug(s_area) << tagName;
 
138
}
 
139
 
 
140
void KoXmlWriter::addCompleteElement(const char* cstr)
 
141
{
 
142
    prepareForChild();
 
143
    writeCString(cstr);
 
144
}
 
145
 
 
146
 
 
147
void KoXmlWriter::addCompleteElement(QIODevice* indev)
 
148
{
 
149
    prepareForChild();
 
150
    bool openOk = indev->open(QIODevice::ReadOnly);
 
151
    Q_ASSERT(openOk);
 
152
    if (!openOk)
 
153
        return;
 
154
    static const int MAX_CHUNK_SIZE = 8 * 1024; // 8 KB
 
155
    QByteArray buffer;
 
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
 
160
            break;
 
161
        d->dev->write(buffer.data(), len);
 
162
    }
 
163
}
 
164
 
 
165
void KoXmlWriter::endElement()
 
166
{
 
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;
 
171
 
 
172
    Tag tag = d->tags.pop();
 
173
//kDebug(s_area) <<" tagName=" << tag.tagName <<" hasChildren=" << tag.hasChildren;
 
174
    if (!tag.hasChildren) {
 
175
        writeCString("/>");
 
176
    } else {
 
177
        if (tag.indentInside && !tag.lastChildIsText) {
 
178
            writeIndent();
 
179
        }
 
180
        writeCString("</");
 
181
        Q_ASSERT(tag.tagName != 0);
 
182
        writeCString(tag.tagName);
 
183
        writeChar('>');
 
184
    }
 
185
}
 
186
 
 
187
void KoXmlWriter::addTextNode(const QByteArray& cstr)
 
188
{
 
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)
 
194
        delete[] escaped;
 
195
}
 
196
 
 
197
void KoXmlWriter::addTextNode(const char* cstr)
 
198
{
 
199
    prepareForTextNode();
 
200
    char* escaped = escapeForXML(cstr, -1);
 
201
    writeCString(escaped);
 
202
    if (escaped != d->escapeBuffer)
 
203
        delete[] escaped;
 
204
}
 
205
 
 
206
void KoXmlWriter::addProcessingInstruction(const char* cstr)
 
207
{
 
208
    prepareForTextNode();
 
209
    writeCString("<?");
 
210
    addTextNode(cstr);
 
211
    writeCString("?>");
 
212
}
 
213
 
 
214
void KoXmlWriter::addAttribute(const char* attrName, const QByteArray& value)
 
215
{
 
216
    // Same as the const char* one, but here we know the size
 
217
    writeChar(' ');
 
218
    writeCString(attrName);
 
219
    writeCString("=\"");
 
220
    char* escaped = escapeForXML(value.constData(), value.size());
 
221
    writeCString(escaped);
 
222
    if (escaped != d->escapeBuffer)
 
223
        delete[] escaped;
 
224
    writeChar('"');
 
225
}
 
226
 
 
227
void KoXmlWriter::addAttribute(const char* attrName, const char* value)
 
228
{
 
229
    writeChar(' ');
 
230
    writeCString(attrName);
 
231
    writeCString("=\"");
 
232
    char* escaped = escapeForXML(value, -1);
 
233
    writeCString(escaped);
 
234
    if (escaped != d->escapeBuffer)
 
235
        delete[] escaped;
 
236
    writeChar('"');
 
237
}
 
238
 
 
239
void KoXmlWriter::addAttribute(const char* attrName, double value)
 
240
{
 
241
    QByteArray str;
 
242
    str.setNum(value, 'f', 11);
 
243
    addAttribute(attrName, str.data());
 
244
}
 
245
 
 
246
void KoXmlWriter::addAttribute(const char* attrName, float value)
 
247
{
 
248
    QByteArray str;
 
249
    str.setNum(value, 'f', FLT_DIG);
 
250
    addAttribute(attrName, str.data());
 
251
}
 
252
 
 
253
void KoXmlWriter::addAttributePt(const char* attrName, double value)
 
254
{
 
255
    QByteArray str;
 
256
    str.setNum(value, 'f', 11);
 
257
    str += "pt";
 
258
    addAttribute(attrName, str.data());
 
259
}
 
260
 
 
261
void KoXmlWriter::addAttributePt(const char* attrName, float value)
 
262
{
 
263
    QByteArray str;
 
264
    str.setNum(value, 'f', FLT_DIG);
 
265
    str += "pt";
 
266
    addAttribute(attrName, str.data());
 
267
}
 
268
 
 
269
void KoXmlWriter::writeIndent()
 
270
{
 
271
    // +1 because of the leading '\n'
 
272
    d->dev->write(d->indentBuffer, qMin(indentLevel() + 1,
 
273
                                        s_indentBufferLength));
 
274
}
 
275
 
 
276
void KoXmlWriter::writeString(const QString& str)
 
277
{
 
278
    // cachegrind says .utf8() is where most of the time is spent
 
279
    const QByteArray cstr = str.toUtf8();
 
280
    d->dev->write(cstr);
 
281
}
 
282
 
 
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
 
286
{
 
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
 
293
    for (;;) {
 
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.
 
299
            if (length == -1)
 
300
                length = qstrlen(source);   // expensive...
 
301
            uint newLength = length * 6 + 1; // worst case. 6 is due to &quot; and &apos;
 
302
            char* buffer = new char[ newLength ];
 
303
            destBoundary = buffer + newLength;
 
304
            uint amountOfCharsAlreadyCopied = destination - d->escapeBuffer;
 
305
            memcpy(buffer, d->escapeBuffer, amountOfCharsAlreadyCopied);
 
306
            output = buffer;
 
307
            destination = buffer + amountOfCharsAlreadyCopied;
 
308
        }
 
309
        switch (*src) {
 
310
        case 60: // <
 
311
            memcpy(destination, "&lt;", 4);
 
312
            destination += 4;
 
313
            break;
 
314
        case 62: // >
 
315
            memcpy(destination, "&gt;", 4);
 
316
            destination += 4;
 
317
            break;
 
318
        case 34: // "
 
319
            memcpy(destination, "&quot;", 6);
 
320
            destination += 6;
 
321
            break;
 
322
#if 0 // needed?
 
323
        case 39: // '
 
324
            memcpy(destination, "&apos;", 6);
 
325
            destination += 6;
 
326
            break;
 
327
#endif
 
328
        case 38: // &
 
329
            memcpy(destination, "&amp;", 5);
 
330
            destination += 5;
 
331
            break;
 
332
        case 0:
 
333
            *destination = '\0';
 
334
            return output;
 
335
        default:
 
336
            *destination++ = *src++;
 
337
            continue;
 
338
        }
 
339
        ++src;
 
340
    }
 
341
    // NOTREACHED (see case 0)
 
342
    return output;
 
343
}
 
344
 
 
345
void KoXmlWriter::addManifestEntry(const QString& fullPath, const QString& mediaType)
 
346
{
 
347
    startElement("manifest:file-entry");
 
348
    addAttribute("manifest:media-type", mediaType);
 
349
    addAttribute("manifest:full-path", fullPath);
 
350
    endElement();
 
351
}
 
352
 
 
353
void KoXmlWriter::addConfigItem(const QString & configName, const QString& value)
 
354
{
 
355
    startElement("config:config-item");
 
356
    addAttribute("config:name", configName);
 
357
    addAttribute("config:type",  "string");
 
358
    addTextNode(value);
 
359
    endElement();
 
360
}
 
361
 
 
362
void KoXmlWriter::addConfigItem(const QString & configName, bool value)
 
363
{
 
364
    startElement("config:config-item");
 
365
    addAttribute("config:name", configName);
 
366
    addAttribute("config:type",  "boolean");
 
367
    addTextNode(value ? "true" : "false");
 
368
    endElement();
 
369
}
 
370
 
 
371
void KoXmlWriter::addConfigItem(const QString & configName, int value)
 
372
{
 
373
    startElement("config:config-item");
 
374
    addAttribute("config:name", configName);
 
375
    addAttribute("config:type",  "int");
 
376
    addTextNode(QString::number(value));
 
377
    endElement();
 
378
}
 
379
 
 
380
void KoXmlWriter::addConfigItem(const QString & configName, double value)
 
381
{
 
382
    startElement("config:config-item");
 
383
    addAttribute("config:name", configName);
 
384
    addAttribute("config:type", "double");
 
385
    addTextNode(QString::number(value));
 
386
    endElement();
 
387
}
 
388
 
 
389
void KoXmlWriter::addConfigItem(const QString & configName, float value)
 
390
{
 
391
    startElement("config:config-item");
 
392
    addAttribute("config:name", configName);
 
393
    addAttribute("config:type", "double");
 
394
    addTextNode(QString::number(value));
 
395
    endElement();
 
396
}
 
397
 
 
398
void KoXmlWriter::addConfigItem(const QString & configName, long value)
 
399
{
 
400
    startElement("config:config-item");
 
401
    addAttribute("config:name", configName);
 
402
    addAttribute("config:type", "long");
 
403
    addTextNode(QString::number(value));
 
404
    endElement();
 
405
}
 
406
 
 
407
void KoXmlWriter::addConfigItem(const QString & configName, short value)
 
408
{
 
409
    startElement("config:config-item");
 
410
    addAttribute("config:name", configName);
 
411
    addAttribute("config:type", "short");
 
412
    addTextNode(QString::number(value));
 
413
    endElement();
 
414
}
 
415
 
 
416
void KoXmlWriter::addTextSpan(const QString& text)
 
417
{
 
418
    QMap<int, int> tabCache;
 
419
    addTextSpan(text, tabCache);
 
420
}
 
421
 
 
422
void KoXmlWriter::addTextSpan(const QString& text, const QMap<int, int>& tabCache)
 
423
{
 
424
    int len = text.length();
 
425
    int nrSpaces = 0; // number of consecutive spaces
 
426
    bool leadingSpace = false;
 
427
    QString str;
 
428
    str.reserve(len);
 
429
 
 
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) {
 
434
        QChar ch = text[i];
 
435
        if (ch != ' ') {
 
436
            if (nrSpaces > 0) {
 
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...)
 
443
                if (!leadingSpace) {
 
444
                    str += ' ';
 
445
                    --nrSpaces;
 
446
                }
 
447
                if (nrSpaces > 0) {   // there are more spaces
 
448
                    if (!str.isEmpty())
 
449
                        addTextNode(str);
 
450
                    str.clear();
 
451
                    startElement("text:s");
 
452
                    if (nrSpaces > 1)   // it's 1 by default
 
453
                        addAttribute("text:c", nrSpaces);
 
454
                    endElement();
 
455
                }
 
456
            }
 
457
            nrSpaces = 0;
 
458
            leadingSpace = false;
 
459
        }
 
460
        switch (ch.unicode()) {
 
461
        case '\t':
 
462
            if (!str.isEmpty())
 
463
                addTextNode(str);
 
464
            str.clear();
 
465
            startElement("text:tab");
 
466
            if (tabCache.contains(i))
 
467
                addAttribute("text:tab-ref", tabCache[i] + 1);
 
468
            endElement();
 
469
            break;
 
470
        case '\n':
 
471
            if (!str.isEmpty())
 
472
                addTextNode(str);
 
473
            str.clear();
 
474
            startElement("text:line-break");
 
475
            endElement();
 
476
            break;
 
477
        case ' ':
 
478
            if (i == 0)
 
479
                leadingSpace = true;
 
480
            ++nrSpaces;
 
481
            break;
 
482
        default:
 
483
            str += text[i];
 
484
            break;
 
485
        }
 
486
    }
 
487
    // either we still have text in str or we have spaces in nrSpaces
 
488
    if (!str.isEmpty()) {
 
489
        addTextNode(str);
 
490
    }
 
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);
 
495
        endElement();
 
496
    }
 
497
}
 
498
 
 
499
QIODevice *KoXmlWriter::device() const
 
500
{
 
501
    return d->dev;
 
502
}
 
503
 
 
504
int KoXmlWriter::indentLevel() const
 
505
{
 
506
    return d->tags.size() + d->baseIndentLevel;
 
507
}
 
508
 
 
509
QList<const char*> KoXmlWriter::tagHierarchy() const
 
510
{
 
511
    QList<const char*> answer;
 
512
    foreach(const Tag & tag, d->tags)
 
513
        answer.append(tag.tagName);
 
514
 
 
515
    return answer;
 
516
}
 
517