/*-------------------------------------------------------------------------
* drawElements Quality Program Helper Library
* -------------------------------------------
*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*//*!
* \file
* \brief XML Writer.
*//*--------------------------------------------------------------------*/
#include "qpXmlWriter.h"
#include "deMemory.h"
#include "deInt32.h"
/*------------------------------------------------------------------------
* qpXmlWriter stand-alone implementation.
*----------------------------------------------------------------------*/
#include "deMemPool.h"
#include "dePoolArray.h"
struct qpXmlWriter_s
{
FILE* outputFile;
deBool flushAfterWrite;
deBool xmlPrevIsStartElement;
deBool xmlIsWriting;
int xmlElementDepth;
};
static deBool writeEscaped (qpXmlWriter* writer, const char* str)
{
char buf[256 + 10];
char* d = &buf[0];
const char* s = str;
deBool isEOS = DE_FALSE;
do
{
/* Check for characters that need to be escaped. */
const char* repl = DE_NULL;
switch (*s)
{
case 0: isEOS = DE_TRUE; break;
case '<': repl = "<"; break;
case '>': repl = ">"; break;
case '&': repl = "&"; break;
case '\'': repl = "'"; break;
case '"': repl = """; break;
/* Non-printable characters. */
case 1: repl = "<SOH>"; break;
case 2: repl = "<STX>"; break;
case 3: repl = "<ETX>"; break;
case 4: repl = "<EOT>"; break;
case 5: repl = "<ENQ>"; break;
case 6: repl = "<ACK>"; break;
case 7: repl = "<BEL>"; break;
case 8: repl = "<BS>"; break;
case 11: repl = "<VT>"; break;
case 12: repl = "<FF>"; break;
case 14: repl = "<SO>"; break;
case 15: repl = "<SI>"; break;
case 16: repl = "<DLE>"; break;
case 17: repl = "<DC1>"; break;
case 18: repl = "<DC2>"; break;
case 19: repl = "<DC3>"; break;
case 20: repl = "<DC4>"; break;
case 21: repl = "<NAK>"; break;
case 22: repl = "<SYN>"; break;
case 23: repl = "<ETB>"; break;
case 24: repl = "<CAN>"; break;
case 25: repl = "<EM>"; break;
case 26: repl = "<SUB>"; break;
case 27: repl = "<ESC>"; break;
case 28: repl = "<FS>"; break;
case 29: repl = "<GS>"; break;
case 30: repl = "<RS>"; break;
case 31: repl = "<US>"; break;
default: /* nada */ break;
}
/* Write out char or escape sequence. */
if (repl)
{
s++;
strcpy(d, repl);
d += strlen(repl);
}
else
*d++ = *s++;
/* Write buffer if EOS or buffer full. */
if (isEOS || ((d - &buf[0]) >= 4))
{
*d = 0;
fprintf(writer->outputFile, "%s", buf);
d = &buf[0];
}
} while (!isEOS);
if (writer->flushAfterWrite)
fflush(writer->outputFile);
DE_ASSERT(d == &buf[0]); /* buffer must be empty */
return DE_TRUE;
}
qpXmlWriter* qpXmlWriter_createFileWriter (FILE* outputFile, deBool useCompression, deBool flushAfterWrite)
{
qpXmlWriter* writer = (qpXmlWriter*)deCalloc(sizeof(qpXmlWriter));
if (!writer)
return DE_NULL;
DE_UNREF(useCompression); /* no compression supported. */
writer->outputFile = outputFile;
writer->flushAfterWrite = flushAfterWrite;
return writer;
}
void qpXmlWriter_destroy (qpXmlWriter* writer)
{
DE_ASSERT(writer);
deFree(writer);
}
static deBool closePending (qpXmlWriter* writer)
{
if (writer->xmlPrevIsStartElement)
{
fprintf(writer->outputFile, ">\n");
writer->xmlPrevIsStartElement = DE_FALSE;
}
return DE_TRUE;
}
void qpXmlWriter_flush (qpXmlWriter* writer)
{
closePending(writer);
}
deBool qpXmlWriter_startDocument (qpXmlWriter* writer)
{
DE_ASSERT(writer && !writer->xmlIsWriting);
writer->xmlIsWriting = DE_TRUE;
writer->xmlElementDepth = 0;
writer->xmlPrevIsStartElement = DE_FALSE;
fprintf(writer->outputFile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
return DE_TRUE;
}
static const char* getIndentStr (int indentLevel)
{
static const char s_indentStr[33] = " ";
static const int s_indentStrLen = 32;
return &s_indentStr[s_indentStrLen - deMin32(s_indentStrLen, indentLevel)];
}
deBool qpXmlWriter_endDocument (qpXmlWriter* writer)
{
DE_ASSERT(writer);
DE_ASSERT(writer->xmlIsWriting);
DE_ASSERT(writer->xmlElementDepth == 0);
closePending(writer);
writer->xmlIsWriting = DE_FALSE;
return DE_TRUE;
}
deBool qpXmlWriter_writeString (qpXmlWriter* writer, const char* str)
{
if (writer->xmlPrevIsStartElement)
{
fprintf(writer->outputFile, ">");
writer->xmlPrevIsStartElement = DE_FALSE;
}
return writeEscaped(writer, str);
}
deBool qpXmlWriter_startElement(qpXmlWriter* writer, const char* elementName, int numAttribs, const qpXmlAttribute* attribs)
{
int ndx;
closePending(writer);
fprintf(writer->outputFile, "%s<%s", getIndentStr(writer->xmlElementDepth), elementName);
for (ndx = 0; ndx < numAttribs; ndx++)
{
const qpXmlAttribute* attrib = &attribs[ndx];
fprintf(writer->outputFile, " %s=\"", attrib->name);
switch (attrib->type)
{
case QP_XML_ATTRIBUTE_STRING:
writeEscaped(writer, attrib->stringValue);
break;
case QP_XML_ATTRIBUTE_INT:
{
char buf[64];
sprintf(buf, "%d", attrib->intValue);
writeEscaped(writer, buf);
break;
}
case QP_XML_ATTRIBUTE_BOOL:
writeEscaped(writer, attrib->boolValue ? "True" : "False");
break;
default:
DE_ASSERT(DE_FALSE);
}
fprintf(writer->outputFile, "\"");
}
writer->xmlElementDepth++;
writer->xmlPrevIsStartElement = DE_TRUE;
return DE_TRUE;
}
deBool qpXmlWriter_endElement (qpXmlWriter* writer, const char* elementName)
{
DE_ASSERT(writer && writer->xmlElementDepth > 0);
writer->xmlElementDepth--;
if (writer->xmlPrevIsStartElement) /* leave flag as-is */
{
fprintf(writer->outputFile, " />\n");
writer->xmlPrevIsStartElement = DE_FALSE;
}
else
fprintf(writer->outputFile, "</%s>\n", /*getIndentStr(writer->xmlElementDepth),*/ elementName);
return DE_TRUE;
}
deBool qpXmlWriter_writeBase64 (qpXmlWriter* writer, const deUint8* data, size_t numBytes)
{
static const char s_base64Table[64] =
{
'A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z',
'0','1','2','3','4','5','6','7','8','9','+','/'
};
int numWritten = 0;
size_t srcNdx = 0;
deBool writeIndent = DE_TRUE;
const char* indentStr = getIndentStr(writer->xmlElementDepth);
DE_ASSERT(writer && data && (numBytes > 0));
/* Close and pending writes. */
closePending(writer);
/* Loop all input chars. */
while (srcNdx < numBytes)
{
size_t numRead = (size_t)deMin32(3, (int)(numBytes - srcNdx));
deUint8 s0 = data[srcNdx];
deUint8 s1 = (numRead >= 2) ? data[srcNdx+1] : 0;
deUint8 s2 = (numRead >= 3) ? data[srcNdx+2] : 0;
char d[5];
srcNdx += numRead;
d[0] = s_base64Table[s0 >> 2];
d[1] = s_base64Table[((s0&0x3)<<4) | (s1>>4)];
d[2] = s_base64Table[((s1&0xF)<<2) | (s2>>6)];
d[3] = s_base64Table[s2&0x3F];
d[4] = 0;
if (numRead < 3) d[3] = '=';
if (numRead < 2) d[2] = '=';
/* Write indent (if needed). */
if (writeIndent)
{
fprintf(writer->outputFile, "%s", indentStr);
writeIndent = DE_FALSE;
}
/* Write data. */
fprintf(writer->outputFile, "%s", &d[0]);
/* EOL every now and then. */
numWritten += 4;
if (numWritten >= 64)
{
fprintf(writer->outputFile, "\n");
numWritten = 0;
writeIndent = DE_TRUE;
}
}
/* Last EOL. */
if (numWritten > 0)
fprintf(writer->outputFile, "\n");
DE_ASSERT(srcNdx == numBytes);
return DE_TRUE;
}
/* Common helper functions. */
deBool qpXmlWriter_writeStringElement (qpXmlWriter* writer, const char* elementName, const char* elementContent)
{
if (!qpXmlWriter_startElement(writer, elementName, 0, DE_NULL) ||
(elementContent && !qpXmlWriter_writeString(writer, elementContent)) ||
!qpXmlWriter_endElement(writer, elementName))
return DE_FALSE;
return DE_TRUE;
}