blob: 9d9520c03605d4f5791cda088772c7392cc33e9c [file] [log] [blame]
/*-------------------------------------------------------------------------
* 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;
bool flushAfterWrite;
bool xmlPrevIsStartElement;
bool xmlIsWriting;
int xmlElementDepth;
};
static bool writeEscaped(qpXmlWriter *writer, const char *str)
{
char buf[256 + 10];
char *d = &buf[0];
const char *s = str;
bool isEOS = false;
do
{
/* Check for characters that need to be escaped. */
const char *repl = DE_NULL;
switch (*s)
{
case 0:
isEOS = true;
break;
case '<':
repl = "&lt;";
break;
case '>':
repl = "&gt;";
break;
case '&':
repl = "&amp;";
break;
case '\'':
repl = "&apos;";
break;
case '"':
repl = "&quot;";
break;
/* Non-printable characters. */
case 1:
repl = "&lt;SOH&gt;";
break;
case 2:
repl = "&lt;STX&gt;";
break;
case 3:
repl = "&lt;ETX&gt;";
break;
case 4:
repl = "&lt;EOT&gt;";
break;
case 5:
repl = "&lt;ENQ&gt;";
break;
case 6:
repl = "&lt;ACK&gt;";
break;
case 7:
repl = "&lt;BEL&gt;";
break;
case 8:
repl = "&lt;BS&gt;";
break;
case 11:
repl = "&lt;VT&gt;";
break;
case 12:
repl = "&lt;FF&gt;";
break;
case 14:
repl = "&lt;SO&gt;";
break;
case 15:
repl = "&lt;SI&gt;";
break;
case 16:
repl = "&lt;DLE&gt;";
break;
case 17:
repl = "&lt;DC1&gt;";
break;
case 18:
repl = "&lt;DC2&gt;";
break;
case 19:
repl = "&lt;DC3&gt;";
break;
case 20:
repl = "&lt;DC4&gt;";
break;
case 21:
repl = "&lt;NAK&gt;";
break;
case 22:
repl = "&lt;SYN&gt;";
break;
case 23:
repl = "&lt;ETB&gt;";
break;
case 24:
repl = "&lt;CAN&gt;";
break;
case 25:
repl = "&lt;EM&gt;";
break;
case 26:
repl = "&lt;SUB&gt;";
break;
case 27:
repl = "&lt;ESC&gt;";
break;
case 28:
repl = "&lt;FS&gt;";
break;
case 29:
repl = "&lt;GS&gt;";
break;
case 30:
repl = "&lt;RS&gt;";
break;
case 31:
repl = "&lt;US&gt;";
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;
fputs(buf, writer->outputFile);
d = &buf[0];
}
} while (!isEOS);
if (writer->flushAfterWrite)
fflush(writer->outputFile);
DE_ASSERT(d == &buf[0]); /* buffer must be empty */
return true;
}
qpXmlWriter *qpXmlWriter_createFileWriter(FILE *outputFile, bool useCompression, bool 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 bool closePending(qpXmlWriter *writer)
{
if (writer->xmlPrevIsStartElement)
{
fprintf(writer->outputFile, ">\n");
writer->xmlPrevIsStartElement = false;
}
return true;
}
void qpXmlWriter_flush(qpXmlWriter *writer)
{
closePending(writer);
}
bool qpXmlWriter_startDocument(qpXmlWriter *writer, bool writeXmlHeader)
{
DE_ASSERT(writer && !writer->xmlIsWriting);
writer->xmlIsWriting = true;
writer->xmlElementDepth = 0;
writer->xmlPrevIsStartElement = false;
if (writeXmlHeader)
{
fprintf(writer->outputFile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
}
return 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)];
}
bool qpXmlWriter_endDocument(qpXmlWriter *writer)
{
DE_ASSERT(writer);
DE_ASSERT(writer->xmlIsWriting);
DE_ASSERT(writer->xmlElementDepth == 0);
closePending(writer);
writer->xmlIsWriting = false;
return true;
}
bool qpXmlWriter_writeString(qpXmlWriter *writer, const char *str)
{
if (writer->xmlPrevIsStartElement)
{
fprintf(writer->outputFile, ">");
writer->xmlPrevIsStartElement = false;
}
return writeEscaped(writer, str);
}
bool 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(false);
}
fprintf(writer->outputFile, "\"");
}
writer->xmlElementDepth++;
writer->xmlPrevIsStartElement = true;
return true;
}
bool 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 = false;
}
else
fprintf(writer->outputFile, "</%s>\n", /*getIndentStr(writer->xmlElementDepth),*/ elementName);
return true;
}
bool qpXmlWriter_writeBase64(qpXmlWriter *writer, const uint8_t *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;
bool writeIndent = 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));
uint8_t s0 = data[srcNdx];
uint8_t s1 = (numRead >= 2) ? data[srcNdx + 1] : 0;
uint8_t 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 = 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 = true;
}
}
/* Last EOL. */
if (numWritten > 0)
fprintf(writer->outputFile, "\n");
DE_ASSERT(srcNdx == numBytes);
return true;
}
/* Common helper functions. */
bool 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 false;
return true;
}