1/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkXMLWriter.h"
9#include "SkStream.h"
10
11SkXMLWriter::SkXMLWriter(bool doEscapeMarkup) : fDoEscapeMarkup(doEscapeMarkup)
12{
13}
14
15SkXMLWriter::~SkXMLWriter()
16{
17    SkASSERT(fElems.count() == 0);
18}
19
20void SkXMLWriter::flush()
21{
22    while (fElems.count())
23        this->endElement();
24}
25
26void SkXMLWriter::addAttribute(const char name[], const char value[])
27{
28    this->addAttributeLen(name, value, strlen(value));
29}
30
31void SkXMLWriter::addS32Attribute(const char name[], int32_t value)
32{
33    SkString    tmp;
34    tmp.appendS32(value);
35    this->addAttribute(name, tmp.c_str());
36}
37
38void SkXMLWriter::addHexAttribute(const char name[], uint32_t value, int minDigits)
39{
40    SkString    tmp("0x");
41    tmp.appendHex(value, minDigits);
42    this->addAttribute(name, tmp.c_str());
43}
44
45void SkXMLWriter::addScalarAttribute(const char name[], SkScalar value)
46{
47    SkString    tmp;
48    tmp.appendScalar(value);
49    this->addAttribute(name, tmp.c_str());
50}
51
52void SkXMLWriter::addText(const char text[], size_t length) {
53    if (fElems.isEmpty()) {
54        return;
55    }
56
57    this->onAddText(text, length);
58
59    fElems.top()->fHasText = true;
60}
61
62void SkXMLWriter::doEnd(Elem* elem)
63{
64    delete elem;
65}
66
67bool SkXMLWriter::doStart(const char name[], size_t length)
68{
69    int level = fElems.count();
70    bool firstChild = level > 0 && !fElems[level-1]->fHasChildren;
71    if (firstChild)
72        fElems[level-1]->fHasChildren = true;
73    Elem** elem = fElems.push();
74    *elem = new Elem(name, length);
75    return firstChild;
76}
77
78SkXMLWriter::Elem* SkXMLWriter::getEnd()
79{
80    Elem* elem;
81    fElems.pop(&elem);
82    return elem;
83}
84
85const char* SkXMLWriter::getHeader()
86{
87    static const char gHeader[] = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
88    return gHeader;
89}
90
91void SkXMLWriter::startElement(const char name[])
92{
93    this->startElementLen(name, strlen(name));
94}
95
96static const char* escape_char(char c, char storage[2])
97{
98    static const char* gEscapeChars[] = {
99        "<&lt;",
100        ">&gt;",
101        //"\"&quot;",
102        //"'&apos;",
103        "&&amp;"
104    };
105
106    const char** array = gEscapeChars;
107    for (unsigned i = 0; i < SK_ARRAY_COUNT(gEscapeChars); i++)
108    {
109        if (array[i][0] == c)
110            return &array[i][1];
111    }
112    storage[0] = c;
113    storage[1] = 0;
114    return storage;
115}
116
117static size_t escape_markup(char dst[], const char src[], size_t length)
118{
119    size_t      extra = 0;
120    const char* stop = src + length;
121
122    while (src < stop)
123    {
124        char        orig[2];
125        const char* seq = escape_char(*src, orig);
126        size_t      seqSize = strlen(seq);
127
128        if (dst)
129        {
130            memcpy(dst, seq, seqSize);
131            dst += seqSize;
132        }
133
134        // now record the extra size needed
135        extra += seqSize - 1;   // minus one to subtract the original char
136
137        // bump to the next src char
138        src += 1;
139    }
140    return extra;
141}
142
143void SkXMLWriter::addAttributeLen(const char name[], const char value[], size_t length)
144{
145    SkString valueStr;
146
147    if (fDoEscapeMarkup)
148    {
149        size_t   extra = escape_markup(NULL, value, length);
150        if (extra)
151        {
152            valueStr.resize(length + extra);
153            (void)escape_markup(valueStr.writable_str(), value, length);
154            value = valueStr.c_str();
155            length += extra;
156        }
157    }
158    this->onAddAttributeLen(name, value, length);
159}
160
161void SkXMLWriter::startElementLen(const char elem[], size_t length)
162{
163    this->onStartElementLen(elem, length);
164}
165
166////////////////////////////////////////////////////////////////////////////////////////
167
168static void write_dom(const SkDOM& dom, const SkDOM::Node* node, SkXMLWriter* w, bool skipRoot)
169{
170    if (!skipRoot)
171    {
172        const char* elem = dom.getName(node);
173        if (dom.getType(node) == SkDOM::kText_Type) {
174            SkASSERT(dom.countChildren(node) == 0);
175            w->addText(elem, strlen(elem));
176            return;
177        }
178
179        w->startElement(elem);
180
181        SkDOM::AttrIter iter(dom, node);
182        const char* name;
183        const char* value;
184        while ((name = iter.next(&value)) != NULL)
185            w->addAttribute(name, value);
186    }
187
188    node = dom.getFirstChild(node, NULL);
189    while (node)
190    {
191        write_dom(dom, node, w, false);
192        node = dom.getNextSibling(node, NULL);
193    }
194
195    if (!skipRoot)
196        w->endElement();
197}
198
199void SkXMLWriter::writeDOM(const SkDOM& dom, const SkDOM::Node* node, bool skipRoot)
200{
201    if (node)
202        write_dom(dom, node, this, skipRoot);
203}
204
205void SkXMLWriter::writeHeader()
206{
207}
208
209// SkXMLStreamWriter
210
211static void tab(SkWStream& stream, int level)
212{
213    for (int i = 0; i < level; i++)
214        stream.writeText("\t");
215}
216
217SkXMLStreamWriter::SkXMLStreamWriter(SkWStream* stream) : fStream(*stream)
218{
219}
220
221SkXMLStreamWriter::~SkXMLStreamWriter()
222{
223    this->flush();
224}
225
226void SkXMLStreamWriter::onAddAttributeLen(const char name[], const char value[], size_t length)
227{
228    SkASSERT(!fElems.top()->fHasChildren && !fElems.top()->fHasText);
229    fStream.writeText(" ");
230    fStream.writeText(name);
231    fStream.writeText("=\"");
232    fStream.write(value, length);
233    fStream.writeText("\"");
234}
235
236void SkXMLStreamWriter::onAddText(const char text[], size_t length) {
237    Elem* elem = fElems.top();
238
239    if (!elem->fHasChildren && !elem->fHasText) {
240        fStream.writeText(">");
241        fStream.newline();
242    }
243
244    tab(fStream, fElems.count() + 1);
245    fStream.write(text, length);
246    fStream.newline();
247}
248
249void SkXMLStreamWriter::onEndElement()
250{
251    Elem* elem = getEnd();
252    if (elem->fHasChildren || elem->fHasText)
253    {
254        tab(fStream, fElems.count());
255        fStream.writeText("</");
256        fStream.writeText(elem->fName.c_str());
257        fStream.writeText(">");
258    } else {
259        fStream.writeText("/>");
260    }
261    fStream.newline();
262    doEnd(elem);
263}
264
265void SkXMLStreamWriter::onStartElementLen(const char name[], size_t length)
266{
267    int level = fElems.count();
268    if (this->doStart(name, length))
269    {
270        // the first child, need to close with >
271        fStream.writeText(">");
272        fStream.newline();
273    }
274
275    tab(fStream, level);
276    fStream.writeText("<");
277    fStream.write(name, length);
278}
279
280void SkXMLStreamWriter::writeHeader()
281{
282    const char* header = getHeader();
283    fStream.write(header, strlen(header));
284    fStream.newline();
285}
286
287////////////////////////////////////////////////////////////////////////////////////////////////
288
289#include "SkXMLParser.h"
290
291SkXMLParserWriter::SkXMLParserWriter(SkXMLParser* parser)
292    : SkXMLWriter(false), fParser(*parser)
293{
294}
295
296SkXMLParserWriter::~SkXMLParserWriter()
297{
298    this->flush();
299}
300
301void SkXMLParserWriter::onAddAttributeLen(const char name[], const char value[], size_t length)
302{
303    SkASSERT(fElems.count() == 0 || (!fElems.top()->fHasChildren && !fElems.top()->fHasText));
304    SkString str(value, length);
305    fParser.addAttribute(name, str.c_str());
306}
307
308void SkXMLParserWriter::onAddText(const char text[], size_t length) {
309    fParser.text(text, SkToInt(length));
310}
311
312void SkXMLParserWriter::onEndElement()
313{
314    Elem* elem = this->getEnd();
315    fParser.endElement(elem->fName.c_str());
316    this->doEnd(elem);
317}
318
319void SkXMLParserWriter::onStartElementLen(const char name[], size_t length)
320{
321    (void)this->doStart(name, length);
322    SkString str(name, length);
323    fParser.startElement(str.c_str());
324}
325
326
327////////////////////////////////////////////////////////////////////////////////////////
328////////////////////////////////////////////////////////////////////////////////////////
329
330#ifdef SK_DEBUG
331
332void SkXMLStreamWriter::UnitTest()
333{
334#ifdef SK_SUPPORT_UNITTEST
335    SkDebugWStream  s;
336    SkXMLStreamWriter       w(&s);
337
338    w.startElement("elem0");
339    w.addAttribute("hello", "world");
340    w.addS32Attribute("dec", 42);
341    w.addHexAttribute("hex", 0x42, 3);
342    w.addScalarAttribute("scalar", -4.2f);
343    w.startElement("elem1");
344        w.endElement();
345        w.startElement("elem1");
346        w.addAttribute("name", "value");
347        w.endElement();
348        w.startElement("elem1");
349            w.startElement("elem2");
350                w.startElement("elem3");
351                w.addAttribute("name", "value");
352                w.endElement();
353            w.endElement();
354            w.startElement("elem2");
355            w.endElement();
356        w.endElement();
357    w.endElement();
358#endif
359}
360
361#endif
362