//**************************************************************************** // 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 Eq // *************************************************************************** bool XmlParser::ParseDeclBegining(const char * szString) { // Parse: S char c = NextChar(); if(!IsSpace(c)) { PreviousChar(); return(false); } ParseSpaces(); // Parse: if(!ParseString(szString)) return(false); // Parse: Eq if(!ParseEq()) SyntaxError(); return(true); } // *************************************************************************** // Function: ParseXMLDecl // Desc: Parse XML declaration // XMLDecl ::= '' // Marker: '' 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 pElem(ParseComment()); if(0 == pElem.get()) break; } } // *************************************************************************** // Function: ParseComment // Desc: Parse comment and construct an element // Comment ::= '' // *************************************************************************** ElementComment* XmlParser::ParseComment() { // Check the start of the comment if(!ParseString("