/*H*************************************************************************************************/
/*!
\File xmlparse.c
\Description
\verbatim
This is a simple XML parser for use in controlled situations. It should not
be used with arbitrary XML from uncontrolled hosts (i.e., test its parsing against
a particular host before using it). This only implement a small subset of full
XML and while suitable for many applications, it is easily confused by badly
formed XML or by some of the legitimate but convoluted XML conventions.
In particular, this parser cannot handle degenerate empty elements (i.e.,
will confuse it). Empty elements must be of proper XML form
. Only the
predefined entity types (< > & ' ") are supported. This module
does not support unicode values.
\endverbatim
\Copyright
Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED.
\Version 1.0 01/30/2002 (gschaefer) First Version
*/
/*************************************************************************************************H*/
/*** Include files ********************************************************************************/
#include
#include
#include "DirtySDK/platform.h"
#include "DirtySDK/dirtysock/dirtylib.h"
#include "DirtySDK/xml/xmlparse.h"
/*** Type Definitions *****************************************************************************/
/*** Variables ************************************************************************************/
static const unsigned char _Xml_TopDecode[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 16, 32, 48, 64, 80, 96,112,128,144, 0, 0, 0, 0, 0, 0,
0,160,176,192,208,224,240, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,160,176,192,208,224,240, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static const unsigned char _Xml_BtmDecode[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
//
static const uint8_t _Xml_CDataTrailer[3] =
{ ']', ']', '>' };
/*** Private Functions ****************************************************************************/
/*F************************************************************************************************/
/*!
\Function _ParseNumber
\Description
Convert a number from string to integer form
\Input pData - pointer to source string
\Input pValue - pointer to output number
\Output - Pointer to next string element
\Version 04/19/2002 (GWS)
*/
/************************************************************************************************F*/
static const unsigned char *_ParseNumber(const unsigned char *pData, int32_t *pValue)
{
for (*pValue = 0; (*pData >= '0') && (*pData <= '9'); ++pData)
{
*pValue = (*pValue * 10) + (*pData & 15);
}
return(pData);
}
/*F************************************************************************************************/
/*!
\Function _XmlContentChar
\Description
Translate an encoded content character to ASCII.
\Input pXml - pointer to raw xml source
\Input pData - pointer to translated output character
\Output - Pointer to next xml source item
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
static const unsigned char *_XmlContentChar(const unsigned char *pXml, unsigned char *pData)
{
uint32_t uNumber;
// default to bogus
*pData = '~';
// coded values
if (pXml[0] == '#')
{
// hex
if (pXml[1] == 'x')
{
// convert hex to integer
for (uNumber = 0, pXml += 2; (*pXml != '\0') && (_Xml_BtmDecode[*pXml] > 0); ++pXml)
{
uNumber = (uNumber << 4) | _Xml_BtmDecode[*pXml];
}
// truncate to 8 bits
*pData = (unsigned char)uNumber;
}
// decimal
else
{
// convert decimal to integer
for (uNumber = 0, pXml += 1; (*pXml >= '0') && (*pXml <= '9'); ++pXml)
{
uNumber = (uNumber * 10) + (*pXml & 15);
}
// truncate to 8 bits
*pData = (unsigned char)uNumber;
}
}
// &
else if ((pXml[0] == 'a') && (pXml[1] == 'm') && (pXml[2] == 'p'))
{
pXml += 3;
*pData = '&';
}
// '
else if ((pXml[0] == 'a') && (pXml[1] == 'p') && (pXml[2] == 'o') && (pXml[3] == 's'))
{
pXml += 4;
*pData = '\'';
}
// "
else if ((pXml[0] == 'q') && (pXml[1] == 'u') && (pXml[2] == 'o') && (pXml[3] == 't'))
{
pXml += 4;
*pData = '"';
}
// <
else if ((pXml[0] == 'l') && (pXml[1] == 't'))
{
pXml += 2;
*pData = '<';
}
// >
else if ((pXml[0] == 'g') && (pXml[1] == 't'))
{
pXml += 2;
*pData = '>';
}
// skip the terminator if present
if (*pXml == ';')
{
pXml += 1;
}
// return updated pointer
return(pXml);
}
/*F************************************************************************************************/
/*!
\Function _XmlContentFind
\Description
Locate the content area for the current element
\Input _pXml - pointer to xml element (start tag)
\Output - Pointer to content area (NULL=no content)
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
static const unsigned char *_XmlContentFind(const char *_pXml)
{
const unsigned char *pXml = (const unsigned char *)_pXml;
// we should be pointing at an element start
if ((pXml == NULL) || (*pXml != '<'))
{
pXml = NULL;
}
// find end of element
else
{
while ((*pXml != 0) && (*pXml != '>'))
{
++pXml;
}
if (*pXml != 0)
{
pXml = (pXml[-1] == '/' ? NULL : pXml+1);
}
}
// return content pointer
return(pXml);
}
/*F************************************************************************************************/
/*!
\Function _XmlAttribFind
\Description
Locate a named attribute within an element
\Input _pXml - pointer to xml element (start tag)
\Input _pAttrib - pointer to attribute to find
\Output
unsigned char * - pointer to attribute value (NULL=no content)
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
static const unsigned char *_XmlAttribFind(const char *_pXml, const char *_pAttrib)
{
int32_t iIndex;
unsigned char uTerm;
const unsigned char *pMatch = NULL;
const unsigned char *pXml = (const unsigned char *)_pXml;
const unsigned char *pAttrib = (const unsigned char *)_pAttrib;
// we should be pointing at an element start
if ((pXml != NULL) && (*pXml == '<'))
{
// skip past element name
for (; (*pXml != 0) && (*pXml > ' '); ++pXml)
;
// parse all the attributes
for (;;)
{
// skip past the white space
for (; (*pXml != 0) && (*pXml <= ' '); ++pXml)
;
// see if this is the attribute we want
for (iIndex = 0; (pXml[iIndex] != 0) && (pXml[iIndex] == pAttrib[iIndex]); ++iIndex)
;
// see if we reached end of attribute name
if (pAttrib[iIndex] == 0)
{
// skip past white space
for (pXml += iIndex; (*pXml != 0) && (*pXml <= ' '); ++pXml)
;
// see if we found the matching attribute
if (*pXml == '=')
{
// find the start of the data
for (pMatch = pXml+1; (*pMatch != 0) && (*pMatch <= ' '); ++pMatch)
;
break;
}
}
// find end of attribute name
for (; (*pXml != 0) && (*pXml != '>') && (*pXml != '='); ++pXml)
;
// make sure this is an attribute assignment
if (*pXml != '=')
{
break;
}
// skip equals and white space
for (++pXml; (*pXml != 0) && (*pXml <= ' '); ++pXml)
;
// see if the data is quoted
if ((*pXml == '"') || (*pXml == '\''))
{
// find ending quote
for (uTerm = *pXml++; (*pXml != 0) && (*pXml != uTerm); ++pXml)
;
// if we found a quote, skip it
if (*pXml == uTerm)
{
++pXml;
}
}
// just skip till next space
else
{
for (; (*pXml != 0) && (*pXml > ' '); ++pXml)
;
}
}
}
// return the match point
return(pMatch);
}
/*F************************************************************************************************/
/*!
\Function _XmlSkipCDataHeader
\Description
Determine if the string points to a CDATA header
\Input pXml - pointer to an XML content string
\Input ppContent - pointer to pointer to the start of the content string
\Output - TRUE if the string demarks a CDATA header
\Version 12/14/2006 (kcarpenter)
*/
/************************************************************************************************F*/
static int32_t _XmlSkipCDataHeader(const unsigned char *pXml, const unsigned char **ppContent)
{
int32_t iIndex;
// Compare the CDATA header char-by-char
for (iIndex = 0; iIndex < (signed)sizeof(_Xml_CDataHeader); iIndex++)
{
if (pXml[iIndex] != _Xml_CDataHeader[iIndex])
{
return(FALSE);
}
}
// Pass out content start pointer, if possible
if (ppContent != NULL)
{
*ppContent = pXml + sizeof(_Xml_CDataHeader);
}
return(TRUE);
}
/*F************************************************************************************************/
/*!
\Function _XmlSkipCDataTrailer
\Description
Determine if the string points to a CDATA trailer
\Input pXml - pointer to the possible end of an XML content string
\Input ppAfterContent - pointer to pointer to the data immediately after the trailer
\Output - TRUE if the string demarks a CDATA trailer
\Version 12/14/2006 (kcarpenter)
*/
/************************************************************************************************F*/
static int32_t _XmlSkipCDataTrailer(const unsigned char *pXml, const unsigned char **ppAfterContent)
{
int32_t iIndex;
// Compare the CDATA trailer char-by-char
for (iIndex = 0; iIndex < (signed)sizeof(_Xml_CDataTrailer); iIndex++)
{
if (pXml[iIndex] != _Xml_CDataTrailer[iIndex])
{
return(FALSE);
}
}
// Pass out pointer to after CDATA trailer, if possible
if (ppAfterContent != NULL)
{
*ppAfterContent = pXml + sizeof(_Xml_CDataTrailer);
}
return(TRUE);
}
#if DIRTYCODE_LOGGING
/*F*************************************************************************************/
/*!
\Function _XmlPrintFmtLine
\Description
Print current line buffer to debug output
\Input *pLine - line buffer to print
\Input iLevel - current xml depth
\Input *pStrPrefix - string to prefix each line with
\Version 09/16/2010 (jbrookes)
*/
/*************************************************************************************F*/
static void _XmlPrintFmtLine(const char *pLine, int32_t iLevel, const char *pStrPrefix)
{
// declare const string of 64 spaces which gives us 16 levels of indentiation with four spaces per indent
const char strIndent[] =
" "
" "
" "
" ";
int32_t iOffset;
// figure out offset into tabs string based on level
if ((iOffset = ((sizeof(strIndent)-1)/4 - iLevel)) < 0)
{
iOffset = 0;
}
iOffset *= 4;
// output the string
NetPrintf(("%s%s%s\r\n", pStrPrefix, strIndent + iOffset, pLine));
}
#endif
#if DIRTYCODE_LOGGING
/*F*************************************************************************************/
/*!
\Function _XmlPrintFmtInsertChar
\Description
Insert a character into the print buffer.
\Input **ppLine - current insertion point
\Input **ppXml - current read point
\Input *pLineStart - start of line buffer
\Input iBufSize - size of line buffer
\Input iLevel - current xml depth
\Input *pStrPrefix - string to prefix each line with
\Version 09/16/2010 (jbrookes)
*/
/*************************************************************************************F*/
static void _XmlPrintFmtInsertChar(char **ppLine, const unsigned char **ppXml, char *pLineStart, int32_t iBufSize, int32_t iLevel, const char *pStrPrefix)
{
// add to line
**ppLine = **ppXml;
*ppLine += 1;
*ppXml += 1;
// check for full buffer
if ((*ppLine - pLineStart) == (iBufSize - 1))
{
// flush current line and continue
**ppLine = '\0';
_XmlPrintFmtLine(pLineStart, iLevel, pStrPrefix);
pLineStart[0] = ' ';
*ppLine = pLineStart + 1;
}
}
#endif
#if DIRTYCODE_LOGGING
/*F*************************************************************************************/
/*!
\Function _XmlPrintFmt
\Description
Takes a as input an XML buffer, and prints out a "nicely" formatted version of
the XML data to debug output. Each line of output is appended to text generated
by the format string and arguments.
\Input *pXml - xml to print
\Input *pStrPrefix - string to prefix each line with
\Version 09/16/2010 (jbrookes)
*/
/*************************************************************************************F*/
static void _XmlPrintFmt(const unsigned char *pXml, const char *pStrPrefix)
{
const char *pTagStart, *pTagEnd, *pTmpBuf;
int32_t iLevel, iNewLevel;
char strLine[132], *pLine;
// skip any leading whitespace
while ((*pXml == ' ') || (*pXml == '\t'))
{
pXml += 1;
}
// add xml to buffer
for (iLevel = 0; *pXml != '\0'; )
{
// copy to line buffer until tag end, crlf, or eos
pLine = strLine;
pTagStart = (*pXml == '<') ? (const char *)pXml+1 : NULL;
pTagEnd = NULL;
// check for close tag
iNewLevel = ((*pXml == '<') && (*(pXml+1) == '/')) ? iLevel-2 : iLevel;
for (; (*pXml != '>') && (*pXml != '\r') && (*pXml != '\0'); )
{
if ((*pXml == ' ') && (pTagEnd == NULL))
{
pTagEnd = (const char *)pXml;
}
_XmlPrintFmtInsertChar(&pLine, &pXml, strLine, sizeof(strLine), iLevel, pStrPrefix);
}
if (*pXml == '>')
{
if ((*(pXml-1) != '/') && (*(pXml-1) != '?'))
{
iNewLevel += 1;
}
if (pTagEnd == NULL)
{
pTagEnd = (const char *)pXml;
}
_XmlPrintFmtInsertChar(&pLine, &pXml, strLine, sizeof(strLine), iLevel, pStrPrefix);
}
// scan ahead and see if we have a matching end tag
if (pTagStart != NULL)
{
pTmpBuf = (const char *)pXml;
while ((*pTmpBuf != '>') && (*pTmpBuf != '\r') && (*pTmpBuf != '\0'))
{
if ((*pTmpBuf == '<') && (*(pTmpBuf+1) == '/') && (!strncmp((const char *)pTagStart, (const char *)pTmpBuf+2, (size_t)(pTagEnd-pTagStart))))
{
while ((*pXml != '>') && (*pXml != '\r') && (*pXml != '\0'))
{
_XmlPrintFmtInsertChar(&pLine, &pXml, strLine, sizeof(strLine), iLevel, pStrPrefix);
}
if (*pXml == '>')
{
_XmlPrintFmtInsertChar(&pLine, &pXml, strLine, sizeof(strLine), iLevel, pStrPrefix);
}
iNewLevel--;
break;
}
pTmpBuf++;
}
}
// skip whitespace
while ((*pXml == '\r') || (*pXml == '\n') || (*pXml == '\t') || (*pXml == ' '))
{
pXml++;
}
// null terminate
*pLine = '\0';
// indent based on level
if (iNewLevel < iLevel)
{
iLevel = iNewLevel;
}
// output line
_XmlPrintFmtLine(strLine, iLevel, pStrPrefix);
// index to new level
iLevel = iNewLevel;
}
}
#endif
/*F************************************************************************************************/
/*!
\Function _XmlSkip
\Description
Skip to the next xml element (used to enumerate lists)
\Input *pXml - pointer to xml element (start tag)
\Input *pValid - [optional, out] TRUE if element being skipped is complete, else FALSE
\Output - Pointer to next element at same level
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
static const unsigned char *_XmlSkip(const unsigned char *pXml, uint32_t *pValid)
{
int32_t iIndex;
uint32_t uValid;
// allow NULL pValid
if (pValid == NULL)
{
pValid = &uValid;
}
// assume invalid
*pValid = FALSE;
// make sure reference is valid
if ((pXml == NULL) || (*pXml != '<'))
{
return(NULL);
}
// handle xml decl
if (pXml[1] == '?')
{
// find the end sequence
for (pXml += 2; (pXml[0] != 0) && (pXml[0] != '?') && (pXml[1] != '>'); ++pXml)
;
// skip the terminator
if (pXml[0] != 0)
{
pXml += 2;
}
}
// handle doctype/comment
else if ((pXml[0] == '<') && (pXml[1] == '!'))
{
// count past the < and >
for (iIndex = 1, ++pXml; (pXml[0] != 0) && (iIndex > 0); ++pXml)
{
if (pXml[0] == '>')
{
iIndex -= 1;
}
if (pXml[0] == '<')
{
iIndex += 1;
}
}
}
// handle regular element
else
{
// count past < and >
for (iIndex = 1, ++pXml; (iIndex > 0) && (pXml[0] != 0);)
{
// if we found an element start
if (pXml[0] == '<')
{
// see if this is CDATA, and if so, skip it
if (_XmlSkipCDataHeader(pXml, &pXml))
{
// skip over the CDATA section
while (TRUE)
{
if (*pXml == 0)
{
break;
}
if ((*pXml == ']') && _XmlSkipCDataTrailer(pXml, &pXml))
{
break;
}
++pXml;
}
continue;
}
// if we hit a doctype/comment, then skip it
if ((pXml[0] == '<') && (pXml[1] == '!'))
{
if ((pXml = _XmlSkip(pXml, NULL)) == NULL)
{
return(pXml);
}
else
{
continue;
}
}
// adjust the indent level
iIndex += (pXml[1] != '/' ? 1 : -1);
// find the end of the element
for (++pXml; (pXml[0] != 0) && (pXml[0] != '>'); ++pXml)
;
// dont gobble /> but do skip >
if (pXml[-1] == '/')
{
--pXml;
}
else if (pXml[0] == '>')
{
++pXml;
}
}
// this is the end of an empty element
else if ((pXml[0] == '/') && (pXml[1] == '>'))
{
iIndex -= 1;
pXml += 2;
}
else
{
++pXml;
}
}
// valid?
if (*pXml != '\0')
{
*pValid = 1;
}
else if ((iIndex <= 1) && (pXml[-1] == '>'))
{
*pValid = 1;
}
}
// skip white space
for (; (*pXml != 0) && (*pXml <= ' '); ++pXml)
;
// see if we ran out of data
if (*pXml == 0)
{
pXml = NULL;
}
// return new position
return(pXml);
}
/*** Public functions *****************************************************************************/
/*F************************************************************************************************/
/*!
\Function XmlSkip
\Description
Skip to the next xml element (used to enumerate lists)
\Input *pXml - pointer to xml element (start tag)
\Output - Pointer to next element at same level
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
const char *XmlSkip(const char *pXml)
{
return((const char *)_XmlSkip((const unsigned char *)pXml, NULL));
}
/*F************************************************************************************************/
/*!
\Function XmlComplete
\Description
Determines if the xml element pointed to is complete
\Input *pXml - pointer to xml element (start tag)
\Output
uint32_t - TRUE if the element is complete, else FALSE
\Version 09/24/2010 (jbrookes)
*/
/************************************************************************************************F*/
uint32_t XmlComplete(const char *pXml)
{
uint32_t uValid;
_XmlSkip((const unsigned char *)pXml, &uValid);
return(uValid);
}
/*F************************************************************************************************/
/*!
\Function XmlFind
\Description
Find an element within an xml document
\Input _pXml - pointer to xml document
\Input _pName - element name (x.y.z notation)
\Output - Pointer to named element start
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
const char *XmlFind(const char *_pXml, const char *_pName)
{
int32_t iXmlIndex, iMatchIndex;
const unsigned char *pMatch = NULL;
const unsigned char *pXml = (const unsigned char *)_pXml;
const unsigned char *pName = (const unsigned char *)_pName;
// make sure record is valid
if ((pXml == NULL) || (pXml[0] == 0))
{
return(NULL);
}
// make sure name is valid
if ((pName == NULL) || (pName[0] == 0))
{
return(NULL);
}
// allow "." to specify current tag
if ((*pName == '.') && (*pXml == '<'))
{
pName += 1;
pXml += 1;
}
// scan the data
while (pXml != NULL)
{
// locate the first marker
for (; (pXml[0] != '<') && (pXml[0] != 0); ++pXml)
;
// if we hit an xml decl then skip it
if ((pXml[0] == '<') && (pXml[1] == '?'))
{
pXml = _XmlSkip(pXml, NULL);
continue;
}
// if we hit a doctype/comment, then skip it
if ((pXml[0] == '<') && (pXml[1] == '!'))
{
pXml = _XmlSkip(pXml, NULL);
continue;
}
// if we hit an end tag, then search must be over
if ((pXml[0] == '<') && (pXml[1] == '/'))
{
break;
}
// skip past leading <
if (pXml[0] != 0)
{
++pXml;
}
// see if we found the matching element
for (iXmlIndex = iMatchIndex = 0; (pXml[iXmlIndex] != '\0') && (pName[iMatchIndex] != '\0'); ++iXmlIndex, ++iMatchIndex)
{
// wildcard match?
if ((pName[iMatchIndex] == '%') && (pName[iMatchIndex+1] == '*'))
{
// consume all input until the subsequent character
for (iMatchIndex += 2; (pXml[iXmlIndex] != '\0') && (pXml[iXmlIndex] != pName[iMatchIndex]); ++iXmlIndex)
;
}
else if (pXml[iXmlIndex] != pName[iMatchIndex])
{
break;
}
}
// handle end of data case
if (pXml[iXmlIndex] == '\0')
{
break;
}
// see if we matched
if ((pXml[iXmlIndex] <= ' ') || (pXml[iXmlIndex] == '>') || (pXml[iXmlIndex] == '/'))
{
// handle leaf match
if (pName[iMatchIndex] == '\0')
{
pMatch = pXml-1;
break;
}
// handle node match
if ((pName[iMatchIndex] == '.') && (pXml[iXmlIndex] != '/'))
{
// locate the end of the start tag
for (; (pXml[0] != '\0') && (pXml[0] != '>'); ++pXml)
;
// see if this is the stop point
if (pName[iMatchIndex+1] == '\0')
{
pMatch = (*pXml ? pXml+1 : NULL);
break;
}
// search within this area recursively
if (pXml[0] != '\0')
{
pMatch = (const unsigned char *)XmlFind((const char *)pXml+1, (const char *)pName+iMatchIndex+1);
}
break;
}
}
// skip past mismatch element
pXml = _XmlSkip(pXml-1, NULL);
}
// point to matching data
return((const char *)pMatch);
}
/*F************************************************************************************************/
/*!
\Function XmlNext
\Description
Skip to next element with same name as current
\Input _pXml - pointer to element start
\Output - Pointer to next element start
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
const char *XmlNext(const char *_pXml)
{
int32_t iIndex;
const unsigned char *pMatch;
const unsigned char *pXml = (const unsigned char *)_pXml;
// make sure we are at element start
for (; (*pXml != 0) && (*pXml != '<'); ++pXml)
;
// locate next tag that matches this one
for (pMatch = _XmlSkip(pXml, NULL); pMatch != NULL; pMatch = _XmlSkip(pMatch, NULL))
{
// skip to the element start
for (; (*pMatch != 0) && (*pMatch != '<'); ++pMatch)
;
// see if we found next element of same name
for (iIndex = 0; (pMatch[iIndex] > ' ') && (pMatch[iIndex] != '>') && (pXml[iIndex] > ' ') && (pXml[iIndex] != '>') && (pMatch[iIndex] == pXml[iIndex]); ++iIndex)
;
// check for complete match
if (((pMatch[iIndex] <= ' ') || (pMatch[iIndex] == '>')) && ((pXml[iIndex] <= ' ') || (pXml[iIndex] == '>')))
{
break;
}
}
// return the match
return((const char *)pMatch);
}
/*F************************************************************************************************/
/*!
\Function XmlStep
\Description
Step over tag (regardless of the tab being a start tag or a end tag).
\Input *_pXml - pointer to tag opening character
\Output
const char * - pointer to byte following tag closing character or NULL if at end of document
\Version 09/16/2010 (jbrookes)
*/
/************************************************************************************************F*/
const char *XmlStep(const char *_pXml)
{
const unsigned char *pXml = (const unsigned char *)_pXml;
// make sure we are at tag opening character
for (; (*pXml != 0) && (*pXml != '<'); ++pXml)
;
// step to tag closing character
for (; (*pXml != '\0') && (*pXml != '>'); ++pXml)
;
// skip past end, return NULL if at end of document
return(((*pXml != '\0') && (*(pXml+1) != '\0')) ? (const char *)pXml + 1 : NULL);
}
/*F************************************************************************************************/
/*!
\Function XmlContentGetString
\Description
Return element contents as a string
\Input pXml - pointer to xml document
\Input _pBuffer - string output buffer
\Input iLength - length of output buffer
\Input pDefault - default value if (pXml == NULL)
\Output
int32_t - length of string (-1=nothing copied)
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
int32_t XmlContentGetString(const char *pXml, char *_pBuffer, int32_t iLength, const char *pDefault)
{
int32_t iLen;
const unsigned char *pData;
unsigned char *pBuffer = (unsigned char *)_pBuffer;
int32_t bInCDataSection;
// validate buffer
if ((pBuffer == NULL) || (iLength < 1))
{
return(-1);
}
// locate the content area
pData = _XmlContentFind(pXml);
if (pData != NULL)
{
// skip leading white space
while ((*pData != 0) && (*pData <= ' '))
{
++pData;
}
// Skip CDATA header, if any
bInCDataSection = _XmlSkipCDataHeader(pData, &pData);
// convert the string
for (iLen = 1; (iLen < iLength) && (*pData != 0); ++iLen)
{
if (bInCDataSection)
{
// Just copy the data until we reach the end trailer marker
if (_XmlSkipCDataTrailer(pData, NULL))
{
break;
}
*pBuffer++ = *pData++;
}
else
{
if (*pData == '<')
{
break;
}
if (*pData == '&')
{
pData = _XmlContentChar(pData+1, pBuffer++);
}
else
{
*pBuffer++ = *pData++;
}
}
}
// remove trailing white space
while ((iLen > 1) && (pBuffer[-1] <= ' '))
{
--iLen;
--pBuffer;
}
// terminate buffer
*pBuffer = 0;
}
else if (pDefault != NULL)
{
// copy over the string
for (iLen = 1; (iLen < iLength) && (*pDefault != 0); ++iLen)
{
*pBuffer++ = *pDefault++;
}
// terminate buffer
*pBuffer = 0;
}
else
{
// leave the old string
iLen = 0;
}
// return length (without terminator)
return(iLen-1);
}
/*F************************************************************************************************/
/*!
\Function XmlContentGetInteger
\Description
Return element contents as an integer
\Input pXml - pointer to xml document
\Input iDefault - default value if (pXml == NULL)
\Output
int32_t - element contents as integer
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
int32_t XmlContentGetInteger(const char *pXml, int32_t iDefault)
{
return((int32_t)XmlContentGetInteger64(pXml, iDefault));
}
/*F************************************************************************************************/
/*!
\Function XmlContentGetInteger64
\Description
Return element contents as an integer (64-bit)
\Input pXml - pointer to xml document
\Input iDefault - default value if (pXml == NULL)
\Output
int64_t - element contents as integer
*/
/************************************************************************************************F*/
int64_t XmlContentGetInteger64(const char *pXml, int64_t iDefault)
{
int32_t iSign = 1;
uint64_t uNumber;
const uint8_t *pData;
// locate the content area
if ((pData = _XmlContentFind(pXml)) == NULL)
{
return(iDefault);
}
// skip leading whitespace
while ((*pData != 0) && (*pData <= ' '))
{
++pData;
}
// check for a sign value
if (*pData == '+')
{
iSign = 1;
++pData;
}
if (*pData == '-')
{
iSign = -1;
++pData;
}
// parse the number
for (uNumber = 0; (*pData >= '0') && (*pData <= '9'); ++pData)
{
uNumber = (uNumber * 10) + (*pData & 15);
}
// return final number
return (iSign*uNumber);
}
/*F************************************************************************************************/
/*!
\Function XmlContentGetToken
\Description
Return element contents as a token (a packed sequence of characters)
\Input pXml - pointer to xml document
\Input iDefault - default value if (pXml == NULL)
\Output
int32_t - element contents as token
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
int32_t XmlContentGetToken(const char *pXml, int32_t iDefault)
{
int32_t iToken = ' ';
const unsigned char *pData;
// locate the content area
if ((pData = _XmlContentFind(pXml)) == NULL)
{
return(iDefault);
}
// skip leading white space
while ((*pData != 0) && (*pData <= ' '))
{
++pData;
}
// parse the number
while ((*pData > ' ') && (*pData != '<'))
{
iToken = (iToken << 8) | *pData++;
}
// return the token
return(iToken);
}
/*F************************************************************************************************/
/*!
\Function XmlContentGetDate
\Description
Return epoch seconds for a date
\Input pXml - pointer to xml document
\Input uDefault - default value if (pXml == NULL)
\Output
uint32_t - element contents as epoch seconds
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
uint32_t XmlContentGetDate(const char *pXml, uint32_t uDefault)
{
struct tm tm;
const unsigned char *pData;
// locate the content area
if ((pData = _XmlContentFind(pXml)) == NULL)
{
return(uDefault);
}
// skip leading white space
while ((*pData != 0) && (*pData <= ' '))
{
++pData;
}
// set the unused fields
tm.tm_isdst = -1;
tm.tm_wday = 0;
tm.tm_yday = 0;
// extract the date
pData = _ParseNumber(pData, &tm.tm_year);
if ((*pData == '.') || (*pData == '-'))
++pData;
pData = _ParseNumber(pData, &tm.tm_mon);
if ((*pData == '.') || (*pData == '-'))
++pData;
pData = _ParseNumber(pData, &tm.tm_mday);
if ((*pData == ' ') || (*pData == 'T'))
++pData;
pData = _ParseNumber(pData, &tm.tm_hour);
if (*pData == ':')
++pData;
pData = _ParseNumber(pData, &tm.tm_min);
if (*pData == ':')
++pData;
_ParseNumber(pData, &tm.tm_sec);
// validate the fields
if ((tm.tm_year < 1970) || (tm.tm_year > 2099) || (tm.tm_mon < 1) || (tm.tm_mon > 12) || (tm.tm_mday < 1) || (tm.tm_mday > 31))
{
return(uDefault);
}
if ((tm.tm_hour < 0) || (tm.tm_hour > 23) || (tm.tm_min < 0) || (tm.tm_min > 59) || (tm.tm_sec < 0) || (tm.tm_sec > 61))
{
return(uDefault);
}
// return epoch time
tm.tm_mon -= 1;
tm.tm_year -= 1900;
return((uint32_t)ds_timetosecs(&tm));
}
/*F************************************************************************************************/
/*!
\Function XmlContentGetAddress
\Description
Parse element contents as an internet dot-notation address and return as an integer.
\Input pXml - pointer to xml document
\Input iDefault - default value if (pXml == NULL)
\Output
int32_t - element contents as integer
\Version 10/07/2005 (JLB)
*/
/************************************************************************************************F*/
int32_t XmlContentGetAddress(const char *pXml, int32_t iDefault)
{
const unsigned char *pData;
uint32_t iAddr, iQuad, iValue;
// locate the content area
if ((pData = _XmlContentFind(pXml)) == NULL)
{
return(iDefault);
}
// parse address
for (iAddr = iQuad = 0; iQuad < 4; iQuad++, pData++)
{
// parse current digit
for (iValue = 0; (*pData >= '0') && (*pData <= '9'); pData++)
{
iValue = (iValue*10) + (*pData & 15);
}
// verify digit
if ((iQuad < 3) && (*pData != '.'))
{
iAddr = iDefault;
break;
}
// accumulate digit in address
iAddr <<= 8;
iAddr |= iValue;
}
// return to caller
return(iAddr);
}
/*F************************************************************************************************/
/*!
\Function XmlContentGetBinary
\Description
Return binary encoded data
\Input pXml - pointer to xml document
\Input pBuffer - pointer to buffer to copy binary data to
\Input iLength - length of buffer pointed to by pBuffer
\Output
int32_t - negative if error, length otherwise
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
int32_t XmlContentGetBinary(const char *pXml, char *pBuffer, int32_t iLength)
{
int32_t iCount;
const unsigned char *pData;
// locate the content area
if ((pData = _XmlContentFind(pXml)) == NULL)
{
return(0);
}
// skip leading white space
while ((*pData != 0) && (*pData <= ' '))
{
++pData;
}
// see if they just want the length
if (pBuffer == NULL)
{
for (iCount = 0; (pData[0] >= '0') && (pData[1] >= '0'); pData += 2)
{
++iCount;
}
return(iCount);
}
// make sure buffer has a valid length
if (iLength < 0)
{
return(-1);
}
// attempt to copy over data
for (iCount = 0; (iCount < iLength) && (pData[0] >= '0') && (pData[1] >= '0'); ++iCount)
{
*pBuffer++ = _Xml_TopDecode[pData[0]] | _Xml_BtmDecode[pData[1]];
pData += 2;
}
// return the length
return(iCount);
}
/*F************************************************************************************************/
/*!
\Function XmlAttribGetString
\Description
Return element attribute as a string
\Input pXml - pointer to xml element
\Input pAttrib - name of attribute
\Input pBuffer - output string buffer
\Input iLength - length of string buffer
\Input pDefault - default string if (pXml == NULL || pAttrib not found)
\Output
int32_t - length of result string (-1=nothing copied)
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
int32_t XmlAttribGetString(const char *pXml, const char *pAttrib, char *pBuffer, int32_t iLength, const char *pDefault)
{
int32_t iLen;
unsigned char uTerm;
const unsigned char *pData;
// validate buffer
if ((pBuffer == NULL) || (iLength < 1))
{
return(-1);
}
// locate the content area
pData = _XmlAttribFind(pXml, pAttrib);
if (pData != NULL)
{
// skip leading white space
while ((*pData != 0) && (*pData <= ' '))
{
++pData;
}
// see if there is a terminator
if ((*pData == '"') || (*pData == '\''))
{
uTerm = *pData++;
}
else
{
uTerm = 0;
}
// convert the string
for (iLen = 1; (iLen < iLength) && (*pData != uTerm) && (*pData != 0) && (*pData != '>'); ++iLen)
{
if (*pData == '&')
{
pData = _XmlContentChar(pData+1, (unsigned char *)pBuffer++);
}
else
{
*pBuffer++ = *pData++;
}
}
// terminate buffer
*pBuffer = 0;
}
else if (pDefault != NULL)
{
// copy over the string
for (iLen = 1; (iLen < iLength) && (*pDefault != 0); ++iLen)
{
*pBuffer++ = *pDefault++;
}
// terminate buffer
*pBuffer = 0;
}
else
{
// leave the old value
iLen = 0;
}
// return length (without terminator)
return(iLen-1);
}
/*F************************************************************************************************/
/*!
\Function XmlAttribGetInteger
\Description
Return element attribute as an integer
\Input pXml - pointer to xml element
\Input pAttrib - name of attribute
\Input iDefault - default value if (pXml == NULL || pAttrib not found)
\Output
int32_t - attibute value as integer
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
int32_t XmlAttribGetInteger(const char *pXml, const char *pAttrib, int32_t iDefault)
{
return((int32_t)XmlAttribGetInteger64(pXml, pAttrib, iDefault));
}
/*F************************************************************************************************/
/*!
\Function XmlAttribGetInteger64
\Description
Return element attribute as an integer (64-bit)
\Input pXml - pointer to xml element
\Input pAttrib - name of attribute
\Input iDefault - default value if (pXml == NULL || pAttrib not found)
\Output
int64_t - attibute value as integer
*/
/************************************************************************************************F*/
int64_t XmlAttribGetInteger64(const char *pXml, const char *pAttrib, int64_t iDefault)
{
int32_t iSign = 1;
uint64_t uNumber;
const unsigned char *pData;
// locate the content area
pData = _XmlAttribFind(pXml, pAttrib);
if (pData == NULL)
{
return(iDefault);
}
// skip leading white space
while ((*pData != 0) && (*pData <= ' '))
{
++pData;
}
// skip terminator if present
if ((*pData == '"') || (*pData == '\''))
{
++pData;
}
// check for a sign value
if (*pData == '+')
{
iSign = 1;
++pData;
}
if (*pData == '-')
{
iSign = -1;
++pData;
}
// parse the number
for (uNumber = 0; (*pData >= '0') && (*pData <= '9'); ++pData)
{
uNumber = (uNumber * 10) + (*pData & 15);
}
// check for symbol true
if (((pData[0]|32) == 't') && ((pData[1]|32) == 'r') && ((pData[2]|32) == 'u') && ((pData[3]|32) == 'e'))
{
iSign = 1;
uNumber = 1;
}
// check for symbol false
if (((pData[0]|32) == 'f') && ((pData[1]|32) == 'a') && ((pData[2]|32) == 'l') && ((pData[3]|32) == 's') && ((pData[4]|32) == 'e'))
{
iSign = 1;
uNumber = 0;
}
// return final value
return(iSign*uNumber);
}
/*F************************************************************************************************/
/*!
\Function XmlAttribGetToken
\Description
Return element attribute as a token
\Input pXml - pointer to xml element
\Input pAttrib - name of attribute
\Input iDefault - default value if (pXml == NULL || pAttrib not found)
\Output
int32_t - attribute value as a token
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
int32_t XmlAttribGetToken(const char *pXml, const char *pAttrib, int32_t iDefault)
{
int32_t iToken = ' ';
unsigned char uTerm = 0;
const unsigned char *pData;
// locate the content area
pData = _XmlAttribFind(pXml, pAttrib);
if (pData == NULL)
{
return(iDefault);
}
// skip leading white space
while ((*pData != 0) && (*pData <= ' '))
{
++pData;
}
// skip terminator if present
if ((*pData == '"') || (*pData == '\''))
{
uTerm = *pData++;
}
// parse the number
while ((*pData > ' ') && (*pData != uTerm) && (*pData != '>') && (*pData != 0))
{
iToken = (iToken << 8) | *pData++;
}
// return final value
return(iToken);
}
/*F************************************************************************************************/
/*!
\Function XmlAttribGetDate
\Description
Return epoch seconds for a date
\Input pXml - pointer to xml element
\Input pAttrib - name of attribute
\Input uDefault - default value if (pXml == NULL || pAttrib not found)
\Output
uint32_t - attribute value as epoch seconds
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
uint32_t XmlAttribGetDate(const char *pXml, const char *pAttrib, uint32_t uDefault)
{
struct tm tm;
const unsigned char *pData;
// locate the content area
pData = _XmlAttribFind(pXml, pAttrib);
if (pData == NULL)
{
return(uDefault);
}
// skip leading white space
while ((*pData != 0) && (*pData <= ' '))
{
++pData;
}
// skip terminator if present
if ((*pData == '"') || (*pData == '\''))
{
++pData;
}
// set the unused fields
tm.tm_isdst = -1;
tm.tm_wday = 0;
tm.tm_yday = 0;
// extract the date
pData = _ParseNumber(pData, &tm.tm_year);
if ((*pData == '.') || (*pData == '-'))
++pData;
pData = _ParseNumber(pData, &tm.tm_mon);
if ((*pData == '.') || (*pData == '-'))
++pData;
pData = _ParseNumber(pData, &tm.tm_mday);
if ((*pData == ' ') || (*pData == 'T'))
++pData;
pData = _ParseNumber(pData, &tm.tm_hour);
if (*pData == ':')
++pData;
pData = _ParseNumber(pData, &tm.tm_min);
if (*pData == ':')
++pData;
_ParseNumber(pData, &tm.tm_sec);
// validate the fields
if ((tm.tm_year < 1970) || (tm.tm_year > 2099) || (tm.tm_mon < 1) || (tm.tm_mon > 12) || (tm.tm_mday < 1) || (tm.tm_mday > 31))
{
return(uDefault);
}
if ((tm.tm_hour < 0) || (tm.tm_hour > 23) || (tm.tm_min < 0) || (tm.tm_min > 59) || (tm.tm_sec < 0) || (tm.tm_sec > 61))
{
return(uDefault);
}
// return epoch time
tm.tm_mon -= 1;
tm.tm_year -= 1900;
return((uint32_t)ds_timetosecs(&tm));
}
//////////////////////////////////////////////////////////////////////////////////////////
/*F************************************************************************************************/
/*!
\Function XmlConvEpoch2Date
\Description
Convert epoch to date components
\Input uEpoch - epoch to convert to date components
\Input pYear - pointer to storage for year
\Input pMonth - pointer to storage for month
\Input pDay - pointer to storage for day
\Input pHour - pointer to storage for hour
\Input pMinute - pointer to storage for minute
\Input pSecond - pointer to storage for second
\Output
int32_t - negative=error, zero=success
\Version 01/30/2002 (GWS)
*/
/************************************************************************************************F*/
int32_t XmlConvEpoch2Date(uint32_t uEpoch, int32_t *pYear, int32_t *pMonth, int32_t *pDay, int32_t *pHour, int32_t *pMinute, int32_t *pSecond)
{
int32_t iResult = -1;
struct tm tm;
// convert to calendar time
if (ds_secstotime(&tm, uEpoch) != NULL)
{
// return the fields
if (pYear != NULL)
*pYear = tm.tm_year;
if (pMonth != NULL)
*pMonth = tm.tm_mon;
if (pDay != NULL)
*pDay = tm.tm_mday;
if (pHour != NULL)
*pHour = tm.tm_hour;
if (pMinute != NULL)
*pMinute = tm.tm_min;
if (pSecond != NULL)
*pSecond = tm.tm_sec;
// return success
iResult = 0;
}
return(iResult);
}
#if DIRTYCODE_LOGGING
/*F*************************************************************************************/
/*!
\Function XmlPrintFmtCode
\Description
Takes a as input an XML buffer, and prints out a "nicely" formatted version of
the XML data to debug output. Each line of output is appended to text generated
by the format string and arguments.
\Input *pXml - xml to print
\Input *pFormat - printf style format string
\Input ... - variable-argument list
\Version 09/15/2010 (jbrookes)
*/
/*************************************************************************************F*/
void XmlPrintFmtCode(const char *pXml, const char *pFormat, ...)
{
char strPrefix[128];
va_list pFmtArgs;
// format prefix string
va_start(pFmtArgs, pFormat);
ds_vsnzprintf(strPrefix, sizeof(strPrefix), pFormat, pFmtArgs);
va_end(pFmtArgs);
_XmlPrintFmt((const unsigned char *)pXml, strPrefix);
}
#endif