1/*-------------------------------------------------------------------------
2 * drawElements Quality Program Helper Library
3 * -------------------------------------------
4 *
5 * Copyright 2014 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief XML Writer.
22 *//*--------------------------------------------------------------------*/
23
24#include "qpXmlWriter.h"
25
26#include "deMemory.h"
27#include "deInt32.h"
28
29/*------------------------------------------------------------------------
30 * qpXmlWriter stand-alone implementation.
31 *----------------------------------------------------------------------*/
32
33#include "deMemPool.h"
34#include "dePoolArray.h"
35
36struct qpXmlWriter_s
37{
38	FILE*				outputFile;
39
40	deBool				xmlPrevIsStartElement;
41	deBool				xmlIsWriting;
42	int					xmlElementDepth;
43};
44
45static deBool writeEscaped (qpXmlWriter* writer, const char* str)
46{
47	char		buf[256 + 10];
48	char*		d		= &buf[0];
49	const char*	s		= str;
50	deBool		isEOS	= DE_FALSE;
51
52	do
53	{
54		/* Check for characters that need to be escaped. */
55		const char* repl = DE_NULL;
56		switch (*s)
57		{
58			case 0:		isEOS = DE_TRUE;		break;
59			case '<':	repl = "&lt;";			break;
60			case '>':	repl = "&gt;";			break;
61			case '&':	repl = "&amp;";			break;
62			case '\'':	repl = "&apos;";		break;
63			case '"':	repl = "&quot;";		break;
64
65			/* Non-printable characters. */
66			case 1:		repl = "&lt;SOH&gt;";	break;
67			case 2:		repl = "&lt;STX&gt;";	break;
68			case 3:		repl = "&lt;ETX&gt;";	break;
69			case 4:		repl = "&lt;EOT&gt;";	break;
70			case 5:		repl = "&lt;ENQ&gt;";	break;
71			case 6:		repl = "&lt;ACK&gt;";	break;
72			case 7:		repl = "&lt;BEL&gt;";	break;
73			case 8:		repl = "&lt;BS&gt;";	break;
74			case 11:	repl = "&lt;VT&gt;";	break;
75			case 12:	repl = "&lt;FF&gt;";	break;
76			case 14:	repl = "&lt;SO&gt;";	break;
77			case 15:	repl = "&lt;SI&gt;";	break;
78			case 16:	repl = "&lt;DLE&gt;";	break;
79			case 17:	repl = "&lt;DC1&gt;";	break;
80			case 18:	repl = "&lt;DC2&gt;";	break;
81			case 19:	repl = "&lt;DC3&gt;";	break;
82			case 20:	repl = "&lt;DC4&gt;";	break;
83			case 21:	repl = "&lt;NAK&gt;";	break;
84			case 22:	repl = "&lt;SYN&gt;";	break;
85			case 23:	repl = "&lt;ETB&gt;";	break;
86			case 24:	repl = "&lt;CAN&gt;";	break;
87			case 25:	repl = "&lt;EM&gt;";	break;
88			case 26:	repl = "&lt;SUB&gt;";	break;
89			case 27:	repl = "&lt;ESC&gt;";	break;
90			case 28:	repl = "&lt;FS&gt;";	break;
91			case 29:	repl = "&lt;GS&gt;";	break;
92			case 30:	repl = "&lt;RS&gt;";	break;
93			case 31:	repl = "&lt;US&gt;";	break;
94
95			default:	/* nada */				break;
96		}
97
98		/* Write out char or escape sequence. */
99		if (repl)
100		{
101			s++;
102			strcpy(d, repl);
103			d += strlen(repl);
104		}
105		else
106			*d++ = *s++;
107
108		/* Write buffer if EOS or buffer full. */
109		if (isEOS || ((d - &buf[0]) >= 4))
110		{
111			*d = 0;
112			fprintf(writer->outputFile, "%s", buf);
113			d = &buf[0];
114		}
115	} while (!isEOS);
116
117	fflush(writer->outputFile);
118	DE_ASSERT(d == &buf[0]); /* buffer must be empty */
119	return DE_TRUE;
120}
121
122qpXmlWriter* qpXmlWriter_createFileWriter (FILE* outputFile, deBool useCompression)
123{
124	qpXmlWriter* writer = (qpXmlWriter*)deCalloc(sizeof(qpXmlWriter));
125	if (!writer)
126		return DE_NULL;
127
128	DE_UNREF(useCompression); /* no compression supported. */
129
130	writer->outputFile = outputFile;
131
132	return writer;
133}
134
135void qpXmlWriter_destroy (qpXmlWriter* writer)
136{
137	DE_ASSERT(writer);
138
139	deFree(writer);
140}
141
142static deBool closePending (qpXmlWriter* writer)
143{
144	if (writer->xmlPrevIsStartElement)
145	{
146		fprintf(writer->outputFile, ">\n");
147		writer->xmlPrevIsStartElement = DE_FALSE;
148	}
149
150	return DE_TRUE;
151}
152
153void qpXmlWriter_flush (qpXmlWriter* writer)
154{
155	closePending(writer);
156}
157
158deBool qpXmlWriter_startDocument (qpXmlWriter* writer)
159{
160	DE_ASSERT(writer && !writer->xmlIsWriting);
161	writer->xmlIsWriting			= DE_TRUE;
162	writer->xmlElementDepth			= 0;
163	writer->xmlPrevIsStartElement	= DE_FALSE;
164	fprintf(writer->outputFile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
165	return DE_TRUE;
166}
167
168static const char* getIndentStr (int indentLevel)
169{
170	static const char	s_indentStr[33]	= "                                ";
171	static const int	s_indentStrLen	= 32;
172	return &s_indentStr[s_indentStrLen - deMin32(s_indentStrLen, indentLevel)];
173}
174
175deBool qpXmlWriter_endDocument (qpXmlWriter* writer)
176{
177	DE_ASSERT(writer);
178	DE_ASSERT(writer->xmlIsWriting);
179	DE_ASSERT(writer->xmlElementDepth == 0);
180	closePending(writer);
181	writer->xmlIsWriting = DE_FALSE;
182	return DE_TRUE;
183}
184
185deBool qpXmlWriter_writeString (qpXmlWriter* writer, const char* str)
186{
187	if (writer->xmlPrevIsStartElement)
188	{
189		fprintf(writer->outputFile, ">");
190		writer->xmlPrevIsStartElement = DE_FALSE;
191	}
192
193	return writeEscaped(writer, str);
194}
195
196deBool qpXmlWriter_startElement(qpXmlWriter* writer, const char* elementName, int numAttribs, const qpXmlAttribute* attribs)
197{
198	int ndx;
199
200	closePending(writer);
201
202	fprintf(writer->outputFile, "%s<%s", getIndentStr(writer->xmlElementDepth), elementName);
203
204	for (ndx = 0; ndx < numAttribs; ndx++)
205	{
206		const qpXmlAttribute* attrib = &attribs[ndx];
207		fprintf(writer->outputFile, " %s=\"", attrib->name);
208		switch (attrib->type)
209		{
210			case QP_XML_ATTRIBUTE_STRING:
211				writeEscaped(writer, attrib->stringValue);
212				break;
213
214			case QP_XML_ATTRIBUTE_INT:
215			{
216				char buf[64];
217				sprintf(buf, "%d", attrib->intValue);
218				writeEscaped(writer, buf);
219				break;
220			}
221
222			case QP_XML_ATTRIBUTE_BOOL:
223				writeEscaped(writer, attrib->boolValue ? "True" : "False");
224				break;
225
226			default:
227				DE_ASSERT(DE_FALSE);
228		}
229		fprintf(writer->outputFile, "\"");
230	}
231
232	writer->xmlElementDepth++;
233	writer->xmlPrevIsStartElement = DE_TRUE;
234	return DE_TRUE;
235}
236
237deBool qpXmlWriter_endElement (qpXmlWriter* writer, const char* elementName)
238{
239	DE_ASSERT(writer && writer->xmlElementDepth > 0);
240	writer->xmlElementDepth--;
241
242	if (writer->xmlPrevIsStartElement) /* leave flag as-is */
243	{
244		fprintf(writer->outputFile, " />\n");
245		writer->xmlPrevIsStartElement = DE_FALSE;
246	}
247	else
248		fprintf(writer->outputFile, "</%s>\n", /*getIndentStr(writer->xmlElementDepth),*/ elementName);
249
250	return DE_TRUE;
251}
252
253deBool qpXmlWriter_writeBase64 (qpXmlWriter* writer, const deUint8* data, int numBytes)
254{
255	static const char s_base64Table[64] =
256	{
257		'A','B','C','D','E','F','G','H','I','J','K','L','M',
258		'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
259		'a','b','c','d','e','f','g','h','i','j','k','l','m',
260		'n','o','p','q','r','s','t','u','v','w','x','y','z',
261		'0','1','2','3','4','5','6','7','8','9','+','/'
262	};
263
264	int			numWritten	= 0;
265	int			srcNdx		= 0;
266	deBool		writeIndent	= DE_TRUE;
267	const char*	indentStr	= getIndentStr(writer->xmlElementDepth);
268
269	DE_ASSERT(writer && data && (numBytes > 0));
270
271	/* Close and pending writes. */
272	closePending(writer);
273
274	/* Loop all input chars. */
275	while (srcNdx < numBytes)
276	{
277		int		numRead = deMin32(3, numBytes - srcNdx);
278		deUint8	s0 = data[srcNdx];
279		deUint8	s1 = (numRead >= 2) ? data[srcNdx+1] : 0;
280		deUint8	s2 = (numRead >= 3) ? data[srcNdx+2] : 0;
281		char	d[5];
282
283		srcNdx += numRead;
284
285		d[0] = s_base64Table[s0 >> 2];
286		d[1] = s_base64Table[((s0&0x3)<<4) | (s1>>4)];
287		d[2] = s_base64Table[((s1&0xF)<<2) | (s2>>6)];
288		d[3] = s_base64Table[s2&0x3F];
289		d[4] = 0;
290
291		if (numRead < 3) d[3] = '=';
292		if (numRead < 2) d[2] = '=';
293
294		/* Write indent (if needed). */
295		if (writeIndent)
296		{
297			fprintf(writer->outputFile, "%s", indentStr);
298			writeIndent = DE_FALSE;
299		}
300
301		/* Write data. */
302		fprintf(writer->outputFile, "%s", &d[0]);
303
304		/* EOL every now and then. */
305		numWritten += 4;
306		if (numWritten >= 64)
307		{
308			fprintf(writer->outputFile, "\n");
309			numWritten = 0;
310			writeIndent = DE_TRUE;
311		}
312	}
313
314	/* Last EOL. */
315	if (numWritten > 0)
316		fprintf(writer->outputFile, "\n");
317
318	DE_ASSERT(srcNdx == numBytes);
319	return DE_TRUE;
320}
321
322/* Common helper functions. */
323
324deBool qpXmlWriter_writeStringElement (qpXmlWriter* writer, const char* elementName, const char* elementContent)
325{
326	if (!qpXmlWriter_startElement(writer, elementName, 0, DE_NULL) ||
327		(elementContent && !qpXmlWriter_writeString(writer, elementContent)) ||
328		!qpXmlWriter_endElement(writer, elementName))
329		return DE_FALSE;
330
331	return DE_TRUE;
332}
333