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