Files
WrenchBoradWeb/wrenchboard/src/core/XmlParser.cc
T
2019-05-31 11:26:35 -04:00

972 lines
25 KiB
C++

//****************************************************************************
// Filename: XmlElements.h
// Copyright 1999 Daniel X. Pape. All rights reserved.
//
// Description: A set of classes for reading and parsing simple XML files.
//
//****************************************************************************
// Revision History:
// Thursday, July 08, 1999 - Original. Heavily based on "A Simple XML
// Parser" by Sebastien Andrivet. See Documentation.
//****************************************************************************
#include "XmlParser.h"
using std::string;
using std::auto_ptr;
// Macro to get the number of elements in a array
#ifndef elemof
#define elemof(array) (sizeof(array) / sizeof((array)[0]))
#endif
// Our namespace
namespace SimpleXMLParser
{
// ***************************************************************************
// Function: Constructor
// Desc:
// ***************************************************************************
XmlParser::XmlParser()
: source_(0), sourceCurrent_(0), sourceEnd_(0),
line_(1), column_(1), xmlVersion_("1.0")
{
}
// ***************************************************************************
// Function: SyntaxError
// Desc: Syntax error (throw exception)
// ***************************************************************************
void XmlParser::SyntaxError()
{
throw XmlException(line_, column_);
}
// ***************************************************************************
// Function: NextChar
// Desc: Next char (next position)
// ***************************************************************************
char XmlParser::NextChar()
{
if(sourceCurrent_ >= sourceEnd_) return(0);
char c = *sourceCurrent_++;
// Skip \r if any
if(c == '\r')
{
if(sourceCurrent_ >= sourceEnd_) return(0);
c = *sourceCurrent_++;
}
if(c == '\n')
++line_, column_ = 1;
else
++column_;
return(c);
}
// ***************************************************************************
// Function: PreviousChar
// Desc: Previous position
// ***************************************************************************
void XmlParser::PreviousChar()
{
if(sourceCurrent_ - 1 < source_)
sourceCurrent_ = source_;
else
sourceCurrent_ -= 1;
}
// ***************************************************************************
// Function: ParseString
// Desc:
// ***************************************************************************
bool XmlParser::ParseString(const char* str)
{
Bookmark bookmark(*this);
// not end of string ?
while(*str != 0)
{
// Next char
char c = NextChar();
// Same ?
if(c != *str)
{
// Not the same so revert back to
// the previous position
bookmark.Restore();
return(false);
}
// Next char of the string
++str;
}
return(true);
}
// ***************************************************************************
// Function: ParseStringNoCase
// Desc: Read the given string (not case sensitive)
// ***************************************************************************
bool XmlParser::ParseStringNoCase(const char* str)
{
Bookmark bookmark(*this);
// not end of string ?
while(*str != 0)
{
// Next char
char c = NextChar();
// Same (not case sensitive) ?
if(LowCase(c) != LowCase(*str))
{
// Not the same so revert back to
// the previous position
bookmark.Restore();
return(false);
}
// Next char of the string
++str;
}
return(true);
}
// ***************************************************************************
// Function: ParseNumber
// Desc: Read a (decimal) number
// ***************************************************************************
bool XmlParser::ParseNumber(int& n)
{
char c = NextChar();
if(!IsDigit(c))
return(false); // not a number
n = 0;
while(IsDigit(c))
{
// Compute new number
n = n * 10 + c - '0';
// Next char
c = NextChar();
}
// The current char if not part of the number
PreviousChar();
return(true);
}
// ***************************************************************************
// Function: ParseHexNumber
// Desc: Read an hexadecimal number
// ***************************************************************************
bool XmlParser::ParseHexNumber(int& n)
{
char c = NextChar();
if(!IsHexDigit(c))
return(false);
n = 0;
// Read all digits possible
while(IsHexDigit(c))
{
// Compute new number
n = n * 16 + HexDigitValue(c);
// Next char
c = NextChar();
}
// The current char if not part of the number
PreviousChar();
return(true);
}
// ***************************************************************************
// Function: ParseChar
// Desc:
// ***************************************************************************
bool XmlParser::ParseChar(char c)
{
if(NextChar() != c)
{
PreviousChar();
return(false);
}
return(true);
}
// ***************************************************************************
// Function: ParseSpaces
// Desc: Read One or more spaces
// S ::= (#x20 | #x9 | #xD | #xA)+
// ***************************************************************************
bool XmlParser::ParseSpaces()
{
char c = NextChar();
if(!IsSpace(c))
{
PreviousChar();
return(false);
}
do c = NextChar();
while(IsSpace(c));
PreviousChar();
return(true);
}
// ***************************************************************************
// Function: ParseDeclBegining
// Desc: Parse a declaration (like: version = )
// S <szString> Eq
// ***************************************************************************
bool XmlParser::ParseDeclBegining(const char * szString)
{
// Parse: S
char c = NextChar();
if(!IsSpace(c))
{
PreviousChar();
return(false);
}
ParseSpaces();
// Parse: <szString>
if(!ParseString(szString))
return(false);
// Parse: Eq
if(!ParseEq())
SyntaxError();
return(true);
}
// ***************************************************************************
// Function: ParseXMLDecl
// Desc: Parse XML declaration
// XMLDecl ::= '<?xml' VersionInfo EncodingDecl? S? '?>'
// Marker: '<?xml'
// ***************************************************************************
bool XmlParser::ParseXMLDecl()
{
// Parse: '<?xml'
if(!ParseStringNoCase("<?xml"))
return(false);
// Parse: VersionInfo EncodingDecl? SDDecl? S? '?>'
if(!ParseVersionInfo(xmlVersion_))
SyntaxError();
// Parse EncodingDecl (optional)
ParseEncodingDecl();
// Parse S (spaces) (optional)
ParseSpaces();
// Parse end of declaration '?>'
if(!ParseString("?>"))
SyntaxError();
return(true);
}
// ***************************************************************************
// Function: ParseEq
// Desc: Parse equal sign
// Eq ::= S? '=' S?
// Marker: '='
// ***************************************************************************
bool XmlParser::ParseEq()
{
// Record the current position
Bookmark bookmark(*this);
// Parse spaces (optional)
ParseSpaces();
// Is an equal sign ?
if(!ParseChar('='))
{
// No, so revert to the previous position
bookmark.Restore();
return(false);
}
// Skip spaces if any
ParseSpaces();
return(true);
}
// ***************************************************************************
// Function: ParseVersionInfo
// Desc: Parse XML version
// VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ")
// Marker: 'version'
// ***************************************************************************
bool XmlParser::ParseVersionInfo(string& version)
{
// Parse: S 'version' Eq
if(!ParseDeclBegining("version"))
return(false);
// Parse: (' VersionNum ' | " VersionNum ")
char c = NextChar();
if(c != '\'' && c != '\"')
SyntaxError();
// Parse version number and check the delimiter
if(!ParseVersionNum(version) || NextChar() != c)
SyntaxError();
return(true);
}
// ***************************************************************************
// Function: ParseVersionNum
// Desc: Parse XML version number
// VersionNum ::= ([a-zA-Z0-9_.:] | '-')+
// ***************************************************************************
bool XmlParser::ParseVersionNum(string& version)
{
// Record the current position
Bookmark bookmark(*this);
char c = NextChar();
// Is an allowed character ?
if(!IsAlphaDigitEx(c))
return(false);
c = NextChar();
// Get as more char as possible
while(IsAlphaDigitEx(c))
c = NextChar();
// Current character is not part of the version num.
PreviousChar();
// Get the version number
bookmark.GetSubString(version);
return(true);
}
// ***************************************************************************
// Function: ParseEncodingDecl
// Desc: Parse XML encoding declaration
// EncodingDecl ::= S 'encoding' Eq
// ('"' EncName '"' | "'" EncName "'")
// Marker: 'encoding'
// ***************************************************************************
bool XmlParser::ParseEncodingDecl()
{
// Parse: S 'encoding' Eq
if(!ParseDeclBegining("encoding"))
return(false);
// Parse: ('"' EncName '"' | "'" EncName "'")
char c = NextChar();
if(c != '\'' && c != '\"')
SyntaxError();
// Parse encoding name and check delimiter
// Bug#0002: Was ParseEncName() instead of !ParseEncName()
if(!ParseEncName() || NextChar() != c)
SyntaxError();
return(true);
}
// ***************************************************************************
// Function: ParseEncName
// Desc: Parse encoding name
// EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
// ***************************************************************************
bool XmlParser::ParseEncName()
{
char c = NextChar();
// Is an allowed character ?
if(!IsAlpha(c))
return(false);
c = NextChar();
// Get as more char as possible
while(IsAlphaDigitEx(c))
c = NextChar();
// Current character is not part of the version num.
PreviousChar();
// In this version, the encoding is not used
return(true);
}
// ***************************************************************************
// Function: ParseMiscs
// Desc: Parse Comments, spaces, etc.
// Misc*
// Misc ::= Comment | S
// ***************************************************************************
void XmlParser::ParseMiscs()
{
for(;;)
{
// Parse spaces if any
ParseSpaces();
// Parse comment if any
auto_ptr<ElementComment> pElem(ParseComment());
if(0 == pElem.get())
break;
}
}
// ***************************************************************************
// Function: ParseComment
// Desc: Parse comment and construct an element
// Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
// ***************************************************************************
ElementComment* XmlParser::ParseComment()
{
// Check the start of the comment
if(!ParseString("<!--"))
return(0);
// Record the current position to extract later the
// content of the comment
Bookmark bookmark(*this);
for(;;)
{
// Look for the end of the comment
if(ParseString("--"))
{
// Really the end ?
if(!ParseChar('>'))
SyntaxError();
break;
}
// End of document ?
if(NextChar() == 0)
SyntaxError();
}
// Extract the content of the comment
string strComment;
bookmark.GetSubString(strComment, 3);
// Construct an element
return(new ElementComment(strComment));
}
// ***************************************************************************
// Function: ParseName
// Desc: Read a name (letters, digits and special chars)
// Name ::= (Letter | '_' | ':') (NameChar)*
// NameChar ::= Letter | Digit | '.' | '-' |
// '_' | ':' | CombiningChar | Extender
// ***************************************************************************
bool XmlParser::ParseName(string& name)
{
// Record the current position to extract later the name
Bookmark bookmark(*this);
char c = NextChar();
// Is allowed ?
if(!IsAlpha(c) && c != '_' && c != ':')
{
PreviousChar();
return(false);
}
// Get as more (allowed) char as possible
for(;;)
{
c = NextChar();
// Is allowed ?
if(!IsAlphaDigitEx(c))
break;
}
// Current character is not part of the version num.
PreviousChar();
// Extract the name
bookmark.GetSubString(name);
return(true);
}
// ***************************************************************************
// Function: ParseReference
// Desc: Parse reference
// Reference ::= EntityRef | CharRef
// EntityRef ::= '&' Name ';'
// CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';'
// Marker: '&'
// ***************************************************************************
bool XmlParser::ParseReference(char& cRef)
{
// Begin like a reference ?
if(!ParseChar('&'))
return(false);
char c = NextChar();
// EntityRef ? (not a CharRef)
if(c != '#')
{
// It is an EntityRef
PreviousChar();
// Get the name of the reference and check the end (';')
string strReferenceName;
if(!ParseName(strReferenceName) || !ParseChar(';'))
SyntaxError();
// Look for the reference
if(!MapReferenceName(strReferenceName, cRef))
SyntaxError();
return(true);
}
// It is a CharRef
c = NextChar();
// Compute the value (character code)
int n = 0;
// Hexadecimal ?
if(c == 'x')
{
// Get the value
if(!ParseHexNumber(n))
SyntaxError();
}
else
{
PreviousChar();
// Get the value
if(!ParseNumber(n))
SyntaxError();
}
// Check the end of the reference
if(!ParseChar(';'))
SyntaxError();
// Return the character with the computed code
cRef = static_cast<char>(n);
return(true);
}
// ***************************************************************************
// Function: ParseAttValue
// Desc: Parse attribute value
// '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'"
// Marker: '"' | "'"
// ***************************************************************************
bool XmlParser::ParseAttValue(string& value)
{
// Get the value delimiter (quote or apostrophe)
char cDelim = NextChar();
if(cDelim != '\'' && cDelim != '\"')
{
PreviousChar();
return(false);
}
// Record the current position to extract later the value
Bookmark bookmark(*this);
char c = NextChar();
// Search the end of the value
while(c != cDelim)
{
switch(c)
{
case 0: // end of document
SyntaxError();
case '<': // Tag
SyntaxError();
case '&': // Reference
{
PreviousChar();
// Put what we already have in the value
string before;
bookmark.GetSubString(before);
value += before;
char c;
// Get the reference
if(!ParseReference(c))
SyntaxError();
// Put the char in the value
value += c;
// Record the new position (after the reference)
bookmark.Reset();
}
//break;
// fall-though intentional -- DXP
default:
// Next character
c = NextChar();
break;
}
}
// Put the remaining of the value
string remaining;
bookmark.GetSubString(remaining, 1);
value += remaining;
return(true);
}
// ***************************************************************************
// Function: ParseAttribute
// Desc: Parse Attribute
// Attribute ::= Name Eq AttValue
// Marker: Name
// ***************************************************************************
bool XmlParser::ParseAttribute(ElementTag* elem)
{
// Get attribute name
string name;
if(!ParseName(name))
return(false);
// Get attribute value after the equal sign
string value;
if(!ParseEq() || !ParseAttValue(value))
SyntaxError();
// Construct an Attribute object and add it to the element
elem->AddAttribute(name, value);
return(true);
}
// ***************************************************************************
// Function: ParseCDATA
// Desc: Parse CDATA
// CDSect ::= '<![CDATA[' CData ']]>'
// CData ::= (Char* - (Char* ']]>' Char*))
// Marker: '<![CDATA['
// ***************************************************************************
bool XmlParser::ParseCDATA(Element& elem)
{
// Parse: <![CDATA[
if(!ParseString("<![CDATA["))
return(false);
Bookmark bookmark(*this);
// Parse: CData
for(;;)
{
// End of CDATA ?
if(ParseString("]]>"))
break;
// Is character allowed ?
if(!IsXmlChar(NextChar()))
SyntaxError();
}
// Get CDATA content and add it as-is to the value
string strCDATA;
bookmark.GetSubString(strCDATA, 3);
elem.AddValue(strCDATA);
return(true);
}
// ***************************************************************************
// Function: ParseTagBegining
// Desc: Parse start tag and construct an element
// '<' Name (S Attribute)* S?
// Marker: '<'
// ***************************************************************************
ElementTag* XmlParser::ParseTagBegining()
{
// Parse: '<'
if(!ParseChar('<'))
return(0);
// Get the name of the tag
string strName;
if(!ParseName(strName))
SyntaxError();
// Construct an element object
auto_ptr<ElementTag> pElem(new ElementTag(strName));
// Parse: (S Attribute)* S?
while(ParseSpaces() && ParseAttribute(pElem.get()))
;
// return the element to the caller
return(pElem.release());
}
// ***************************************************************************
// Function: ParseETag
// Desc: Parse end tag
// ETag ::= '</' Name S? '>'
// Marker: '</'
// ***************************************************************************
bool XmlParser::ParseETag(Element& elem)
{
// Is an End tag ?
if(!ParseString("</"))
return(false);
// Get the tag name
string endTagName;
if(!ParseName(endTagName))
SyntaxError();
// Start and end tag names must match
if(endTagName != elem.GetName())
SyntaxError();
// Skip spaces
ParseSpaces();
// End of the tag
if(!ParseChar('>'))
SyntaxError();
return(true);
}
// ***************************************************************************
// Function: ParseMarkup
// Desc: Parse markups like comments, CDATA, elements, etc.
// Comment begins with '<!--'
// CDSect begins with '<![CDATA['
// element begins with '<'
// ***************************************************************************
bool XmlParser::ParseMarkup(Element& elem)
{
// Try to read a comment
auto_ptr<ElementComment> comment(ParseComment());
if(0 != comment.get())
{
// Add it to the element
if(!elem.AddChild(comment.release()))
SyntaxError();
return(true);
}
// Try to read a CDATA
if(ParseCDATA(elem))
return(true);
// Try to read an element
auto_ptr<ElementTag> tag(ParseElement());
if(0 != tag.get())
{
if(!elem.AddChild(tag.release()))
SyntaxError();
return(true);
}
return(false);
}
// ***************************************************************************
// Function: ParseContentETag
// Desc: Parse element content and end tag
// content ::= (element | CharData | Reference | CDSect | Comment)*
// CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*)
// Reference begins with '&'
// ***************************************************************************
void XmlParser::ParseContentETag(ElementTag& elem)
{
// Record the current position to extract later the content
Bookmark bookmark(*this);
char c = NextChar();
for(;;)
{
// "]]>" not allowed in content
if(ParseString("]]>"))
SyntaxError();
switch(c)
{
case 0: // End of document
SyntaxError();
break;
case '&':
case '<':
{
PreviousChar();
// Put what we already have in the value
string value;
bookmark.GetSubString(value);
elem.AddValue(value);
// Tag or reference ?
if(c == '&')
{
// Get the reference
if(!ParseReference(c))
SyntaxError();
// Add it to the value
elem.AddValue(c);
}
else
{
// Is an end tag ?
if(ParseETag(elem))
return;
else if(!ParseMarkup(elem))
SyntaxError();
}
bookmark.Reset();
}
break;
default:
break;
}
// Next char
c = NextChar();
}
}
// ***************************************************************************
// Function: ParseElement
// Desc: Parse element and construct an object
// element ::= EmptyElemTag | STag content ETag
// EmptyElemTag ::= '<' Name (S Attribute)* S? '/>'
// STag ::= '<' Name (S Attribute)* S? '>'
// Marker: '<'
// ***************************************************************************
ElementTag* XmlParser::ParseElement()
{
// Begining of element (start tag)
auto_ptr<ElementTag> tag(ParseTagBegining());
if(tag.get() == 0)
return(0);
char c = NextChar();
if(c == '/') // Empty tag ?
{
c = NextChar();
if(c != '>')
SyntaxError();
return(tag.release());
}
// End of the tag
if(c != '>')
SyntaxError();
// Parse the remaining of the element
ParseContentETag(*tag);
return(tag.release());
}
// ***************************************************************************
// Function: ParseDocument
// Desc: Parse document and construct a (root) element
// document ::= XMLDecl? Misc* element Misc*
// ***************************************************************************
Element* XmlParser::ParseDocument()
{
// XML declaration (optional)
ParseXMLDecl();
ParseMiscs();
// Get the root element
auto_ptr<Element> elem(ParseElement());
if(0 == elem.get())
SyntaxError();
ParseMiscs();
return(elem.release());
}
// ***************************************************************************
// Function: Parse
// Desc: parse document
// ***************************************************************************
Element& XmlParser::Parse(const char * szSource, int nSourceSize)
{
source_ = szSource;
sourceCurrent_ = source_;
sourceEnd_ = source_ + nSourceSize;
line_ = column_ = 1;
// Get the root eleement
#ifdef _MSC_VER
rootElem_ = std::auto_ptr<Element>(ParseDocument());
#else
rootElem_.reset(ParseDocument());
#endif
return(*rootElem_);
}
//////////////////////////////////////////////////////////////////////
// Entity references
//////////////////////////////////////////////////////////////////////
// Map a name to a character
struct MapReference
{
const char * szName;
char c;
};
// Predefined refererences
static const MapReference s_MapReference[] =
{
{ "lt", '<' },
{ "gt", '>' },
{ "amp", '&' },
// { "apos", '\"' },
// { "quot", '\'' } DXP
{ "apos", '\'' },
{ "quot", '\"' }
};
// ***************************************************************************
// Function: MapReferenceName
// Desc: Find the reference strName and return its equivalent
// ***************************************************************************
bool XmlParser::MapReferenceName(const string& strName, char& c)
{
for(unsigned int nIndex = 0; nIndex < elemof(s_MapReference); ++nIndex)
{
// Same name ?
if(strName == s_MapReference[nIndex].szName)
{
// return the equivalent
c = s_MapReference[nIndex].c;
return(true);
}
}
return(false);
}
}