improved the streamer interface so it doesn't require text parent. now possible to connect visitor and streamer.

This commit is contained in:
Lee Thomason 2012-02-09 18:16:58 -08:00
parent 43f59307cc
commit 56bdd0259e
3 changed files with 206 additions and 74 deletions

View File

@ -128,9 +128,9 @@ const char* StringPool::Intern( const char* str )
*/
// --------- XMLBase ----------- //
// --------- XMLUtil ----------- //
char* XMLBase::ParseText( char* p, StrPair* pair, const char* endTag, int strFlags )
char* StrPair::ParseText( char* p, const char* endTag, int strFlags )
{
TIXMLASSERT( endTag && *endTag );
@ -141,7 +141,7 @@ char* XMLBase::ParseText( char* p, StrPair* pair, const char* endTag, int strFla
// Inner loop of text parsing.
while ( *p ) {
if ( *p == endChar && strncmp( p, endTag, length ) == 0 ) {
pair->Set( start, p, strFlags );
Set( start, p, strFlags );
return p + length;
}
++p;
@ -150,7 +150,7 @@ char* XMLBase::ParseText( char* p, StrPair* pair, const char* endTag, int strFla
}
char* XMLBase::ParseName( char* p, StrPair* pair )
char* StrPair::ParseName( char* p )
{
char* start = p;
@ -159,12 +159,12 @@ char* XMLBase::ParseName( char* p, StrPair* pair )
return 0;
}
if ( !IsAlpha( *p ) ) {
if ( !XMLUtil::IsAlpha( *p ) ) {
return 0;
}
while( *p && (
IsAlphaNum( (unsigned char) *p )
XMLUtil::IsAlphaNum( (unsigned char) *p )
|| *p == '_'
|| *p == '-'
|| *p == '.'
@ -174,7 +174,7 @@ char* XMLBase::ParseName( char* p, StrPair* pair )
}
if ( p > start ) {
pair->Set( start, p, 0 );
Set( start, p, 0 );
return p;
}
return 0;
@ -185,7 +185,7 @@ char* XMLDocument::Identify( char* p, XMLNode** node )
{
XMLNode* returnNode = 0;
char* start = p;
p = XMLBase::SkipWhiteSpace( p );
p = XMLUtil::SkipWhiteSpace( p );
if( !p || !*p )
{
return 0;
@ -210,18 +210,18 @@ char* XMLDocument::Identify( char* p, XMLNode** node )
static const int cdataHeaderLen = 9;
static const int elementHeaderLen = 1;
if ( XMLBase::StringEqual( p, commentHeader, commentHeaderLen ) ) {
if ( XMLUtil::StringEqual( p, commentHeader, commentHeaderLen ) ) {
returnNode = new (commentPool.Alloc()) XMLComment( this );
returnNode->memPool = &commentPool;
p += commentHeaderLen;
}
else if ( XMLBase::StringEqual( p, elementHeader, elementHeaderLen ) ) {
else if ( XMLUtil::StringEqual( p, elementHeader, elementHeaderLen ) ) {
returnNode = new (elementPool.Alloc()) XMLElement( this );
returnNode->memPool = &elementPool;
p += elementHeaderLen;
}
// fixme: better text detection
else if ( (*p != '<') && XMLBase::IsAlphaNum( *p ) ) {
else if ( (*p != '<') && XMLUtil::IsAlphaNum( *p ) ) {
returnNode = new (textPool.Alloc()) XMLText( this );
returnNode->memPool = &textPool;
p = start; // Back it up, all the text counts.
@ -235,6 +235,20 @@ char* XMLDocument::Identify( char* p, XMLNode** node )
}
bool XMLDocument::Accept( XMLVisitor* visitor ) const
{
if ( visitor->VisitEnter( *this ) )
{
for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() )
{
if ( !node->Accept( visitor ) )
break;
}
}
return visitor->VisitExit( *this );
}
// --------- XMLNode ----------- //
XMLNode::XMLNode( XMLDocument* doc ) :
@ -314,12 +328,12 @@ XMLNode* XMLNode::InsertEndChild( XMLNode* addThis )
}
XMLElement* XMLNode::FirstChildElement( const char* value )
const XMLElement* XMLNode::FirstChildElement( const char* value ) const
{
for( XMLNode* node=firstChild; node; node=node->next ) {
XMLElement* element = node->ToElement();
if ( element ) {
if ( !value || XMLBase::StringEqual( element->Name(), value ) ) {
if ( !value || XMLUtil::StringEqual( element->Name(), value ) ) {
return element;
}
}
@ -328,6 +342,27 @@ XMLElement* XMLNode::FirstChildElement( const char* value )
}
const XMLElement* XMLNode::LastChildElement( const char* value ) const
{
for( XMLNode* node=lastChild; node; node=node->prev ) {
XMLElement* element = node->ToElement();
if ( element ) {
if ( !value || XMLUtil::StringEqual( element->Name(), value ) ) {
return element;
}
}
}
return 0;
}
void XMLNode::DeleteChild( XMLNode* node )
{
TIXMLASSERT( node->parent == this );
TIXMLASSERT( 0 );
}
void XMLNode::Print( XMLStreamer* streamer )
{
for( XMLNode* node = firstChild; node; node=node->next ) {
@ -357,7 +392,7 @@ char* XMLNode::ParseDeep( char* p )
// --------- XMLText ---------- //
char* XMLText::ParseDeep( char* p )
{
p = XMLBase::ParseText( p, &value, "<", StrPair::TEXT_ELEMENT );
p = value.ParseText( p, "<", StrPair::TEXT_ELEMENT );
// consumes the end tag.
if ( p && *p ) {
return p-1;
@ -373,6 +408,12 @@ void XMLText::Print( XMLStreamer* streamer )
}
bool XMLText::Accept( XMLVisitor* visitor ) const
{
return visitor->Visit( *this );
}
// --------- XMLComment ---------- //
XMLComment::XMLComment( XMLDocument* doc ) : XMLNode( doc )
@ -397,19 +438,25 @@ void XMLComment::Print( XMLStreamer* streamer )
char* XMLComment::ParseDeep( char* p )
{
// Comment parses as text.
return XMLBase::ParseText( p, &value, "-->", StrPair::COMMENT );
return value.ParseText( p, "-->", StrPair::COMMENT );
}
bool XMLComment::Accept( XMLVisitor* visitor ) const
{
return visitor->Visit( *this );
}
// --------- XMLAttribute ---------- //
char* XMLAttribute::ParseDeep( char* p )
{
p = XMLBase::ParseText( p, &name, "=", StrPair::ATTRIBUTE_NAME );
p = name.ParseText( p, "=", StrPair::ATTRIBUTE_NAME );
if ( !p || !*p ) return 0;
char endTag[2] = { *p, 0 };
++p;
p = XMLBase::ParseText( p, &value, endTag, StrPair::ATTRIBUTE_VALUE );
p = value.ParseText( p, endTag, StrPair::ATTRIBUTE_VALUE );
if ( value.Empty() ) return 0;
return p;
}
@ -452,14 +499,14 @@ char* XMLElement::ParseAttributes( char* p, bool* closedElement )
// Read the attributes.
while( p ) {
p = XMLBase::SkipWhiteSpace( p );
p = XMLUtil::SkipWhiteSpace( p );
if ( !p || !(*p) ) {
document->SetError( XMLDocument::ERROR_PARSING_ELEMENT, start, Name() );
return 0;
}
// attribute.
if ( XMLBase::IsAlpha( *p ) ) {
if ( XMLUtil::IsAlpha( *p ) ) {
XMLAttribute* attrib = new (document->attributePool.Alloc() ) XMLAttribute( this );
attrib->memPool = &document->attributePool;
@ -508,7 +555,7 @@ char* XMLElement::ParseAttributes( char* p, bool* closedElement )
char* XMLElement::ParseDeep( char* p )
{
// Read the element name.
p = XMLBase::SkipWhiteSpace( p );
p = XMLUtil::SkipWhiteSpace( p );
if ( !p ) return 0;
const char* start = p;
@ -520,7 +567,7 @@ char* XMLElement::ParseDeep( char* p )
++p;
}
p = XMLBase::ParseName( p, &value );
p = value.ParseName( p );
if ( value.Empty() ) return 0;
bool elementClosed=false;
@ -539,7 +586,7 @@ void XMLElement::Print( XMLStreamer* streamer )
// PrintSpace( cfile, depth );
//}
//fprintf( cfile, "<%s", Name() );
streamer->OpenElement( Name(), IsTextParent() );
streamer->OpenElement( Name() );
for( XMLAttribute* attrib=rootAttribute; attrib; attrib=attrib->next ) {
//fprintf( cfile, " " );
@ -554,6 +601,21 @@ void XMLElement::Print( XMLStreamer* streamer )
}
bool XMLElement::Accept( XMLVisitor* visitor ) const
{
if ( visitor->VisitEnter( *this, rootAttribute ) )
{
for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() )
{
if ( !node->Accept( visitor ) )
break;
}
}
return visitor->VisitExit( *this );
}
// --------- XMLDocument ----------- //
XMLDocument::XMLDocument() :
XMLNode( 0 ),
@ -678,7 +740,7 @@ const char* StringStack::Pop() {
*/
XMLStreamer::XMLStreamer( FILE* file ) : fp( file ), depth( 0 ), elementJustOpened( false )
XMLStreamer::XMLStreamer( FILE* file ) : fp( file ), depth( 0 ), elementJustOpened( false ), textDepth( -1 )
{
for( int i=0; i<ENTITY_RANGE; ++i ) {
entityFlag[i] = false;
@ -733,18 +795,18 @@ void XMLStreamer::PrintString( const char* p )
}
}
void XMLStreamer::OpenElement( const char* name, bool textParent )
void XMLStreamer::OpenElement( const char* name )
{
if ( elementJustOpened ) {
SealElement();
}
if ( !TextOnStack() ) {
stack.Push( name );
if ( textDepth < 0 && depth > 0) {
fprintf( fp, "\n" );
PrintSpace( depth );
}
stack.Push( name );
text.Push( textParent ? 'T' : 'e' );
// fixme: can names have entities?
fprintf( fp, "<%s", name );
elementJustOpened = true;
++depth;
@ -764,25 +826,22 @@ void XMLStreamer::CloseElement()
{
--depth;
const char* name = stack.Pop();
bool wasText = TextOnStack();
text.Pop();
if ( elementJustOpened ) {
fprintf( fp, "/>" );
if ( !wasText ) {
fprintf( fp, "\n" );
}
}
else {
if ( !wasText ) {
if ( textDepth < 0 ) {
fprintf( fp, "\n" );
PrintSpace( depth );
}
// fixme can names have entities?
fprintf( fp, "</%s>", name );
if ( !TextOnStack() ) {
fprintf( fp, "\n" );
}
}
if ( textDepth == depth )
textDepth = -1;
if ( depth == 0 )
fprintf( fp, "\n" );
elementJustOpened = false;
}
@ -791,14 +850,13 @@ void XMLStreamer::SealElement()
{
elementJustOpened = false;
fprintf( fp, ">" );
if ( !TextOnStack() ) {
fprintf( fp, "\n" );
}
}
void XMLStreamer::PushText( const char* text )
{
textDepth = depth-1;
if ( elementJustOpened ) {
SealElement();
}

View File

@ -12,7 +12,7 @@
- make constructors protected
- hide copy constructor
- hide = operator
- UTF8 support: isAlpha, etc.
X UTF8 support: isAlpha, etc.
*/
#include <limits.h>
@ -74,6 +74,9 @@ public:
bool Empty() const { return start == end; }
void SetInternedStr( const char* str ) { this->start = (char*) str; this->end = 0; this->flags = 0; }
char* ParseText( char* in, const char* endTag, int strFlags );
char* ParseName( char* in );
private:
enum {
@ -278,16 +281,59 @@ private:
};
*/
class XMLBase
/**
Implements the interface to the "Visitor pattern" (see the Accept() method.)
If you call the Accept() method, it requires being passed a XMLVisitor
class to handle callbacks. For nodes that contain other nodes (Document, Element)
you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves
are simply called with Visit().
If you return 'true' from a Visit method, recursive parsing will continue. If you return
false, <b>no children of this node or its sibilings</b> will be Visited.
All flavors of Visit methods have a default implementation that returns 'true' (continue
visiting). You need to only override methods that are interesting to you.
Generally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting.
You should never change the document from a callback.
@sa XMLNode::Accept()
*/
class XMLVisitor
{
public:
virtual ~XMLVisitor() {}
/// Visit a document.
virtual bool VisitEnter( const XMLDocument& /*doc*/ ) { return true; }
/// Visit a document.
virtual bool VisitExit( const XMLDocument& /*doc*/ ) { return true; }
/// Visit an element.
virtual bool VisitEnter( const XMLElement& /*element*/, const XMLAttribute* /*firstAttribute*/ ) { return true; }
/// Visit an element.
virtual bool VisitExit( const XMLElement& /*element*/ ) { return true; }
/// Visit a declaration
//virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; }
/// Visit a text node
virtual bool Visit( const XMLText& /*text*/ ) { return true; }
/// Visit a comment node
virtual bool Visit( const XMLComment& /*comment*/ ) { return true; }
/// Visit an unknown node
//virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; }
};
class XMLUtil
{
public:
XMLBase() {}
virtual ~XMLBase() {}
static const char* SkipWhiteSpace( const char* p ) { while( isspace( *p ) ) { ++p; } return p; }
static char* SkipWhiteSpace( char* p ) { while( isspace( *p ) ) { ++p; } return p; }
// Anything in the high order range of UTF-8 is assumed to not be whitespace. This isn't
// correct, but simple, and usually works.
static const char* SkipWhiteSpace( const char* p ) { while( IsUTF8Continuation(*p) || isspace( *p ) ) { ++p; } return p; }
static char* SkipWhiteSpace( char* p ) { while( IsUTF8Continuation(*p) || isspace( *p ) ) { ++p; } return p; }
inline static bool StringEqual( const char* p, const char* q, int nChar=INT_MAX ) {
int n = 0;
@ -303,13 +349,8 @@ public:
return false;
}
inline static int IsUTF8Continuation( unsigned char p ) { return p & 0x80; }
inline static int IsAlphaNum( unsigned char anyByte ) { return ( anyByte <= 127 ) ? isalnum( anyByte ) : 1; }
inline static int IsAlpha( unsigned char anyByte ) { return ( anyByte <= 127 ) ? isalpha( anyByte ) : 1; }
static char* ParseText( char* in, StrPair* pair, const char* endTag, int strFlags );
static char* ParseName( char* in, StrPair* pair );
private:
inline static int IsAlphaNum( unsigned char anyByte ) { return ( anyByte < 128 ) ? isalnum( anyByte ) : 1; }
inline static int IsAlpha( unsigned char anyByte ) { return ( anyByte < 128 ) ? isalpha( anyByte ) : 1; }
};
@ -318,25 +359,50 @@ class XMLNode
friend class XMLDocument;
friend class XMLElement;
public:
//void* operator new( size_t size, MemPool* pool );
//void operator delete( void* mem, MemPool* pool );
XMLDocument* GetDocument() { return document; }
XMLNode* InsertEndChild( XMLNode* addThis );
virtual void Print( XMLStreamer* streamer );
virtual XMLElement* ToElement() { return 0; }
virtual XMLText* ToText() { return 0; }
virtual XMLComment* ToComment() { return 0; }
virtual XMLDocument* ToDocument() { return 0; }
const char* Value() const { return value.GetStr(); }
void SetValue( const char* val ) { value.SetInternedStr( val ); }
virtual XMLElement* ToElement() { return 0; }
virtual XMLText* ToText() { return 0; }
virtual XMLComment* ToComment() { return 0; }
const XMLNode* FirstChild() const { return firstChild; }
XMLNode* FirstChild() { return firstChild; }
const XMLElement* FirstChildElement( const char* value=0 ) const;
XMLElement* FirstChildElement( const char* value=0 ) { return const_cast<XMLElement*>(const_cast<const XMLNode*>(this)->FirstChildElement( value )); }
XMLNode* FirstChild() { return firstChild; }
XMLElement* FirstChildElement( const char* value=0 );
const XMLNode* LastChild() const { return lastChild; }
XMLNode* LastChild() { return const_cast<XMLNode*>(const_cast<const XMLNode*>(this)->LastChild() ); }
const XMLElement* LastChildElement( const char* value=0 ) const;
XMLElement* LastChildElement( const char* value=0 ) { return const_cast<XMLElement*>(const_cast<const XMLNode*>(this)->LastChildElement(value) ); }
const XMLNode* PreviousSibling() const { return prev; }
XMLNode* PreviousSibling() { return prev; }
const XMLNode* PreviousSiblingElement( const char* value=0 ) const ;
XMLNode* PreviousSiblingElement( const char* value=0 ) { return const_cast<XMLNode*>(const_cast<const XMLNode*>(this)->PreviousSiblingElement( value ) ); }
const XMLNode* NextSibling() const { return next; }
XMLNode* NextSibling() { return next; }
const XMLNode* NextSiblingElement( const char* value=0 ) const;
XMLNode* NextSiblingElement( const char* value=0 ) { return const_cast<XMLNode*>(const_cast<const XMLNode*>(this)->NextSiblingElement( value ) ); }
XMLNode* InsertEndChild( XMLNode* addThis );
XMLNode* InsertFirstChild( XMLNode* addThis );
XMLNode* InsertAfterChild( XMLNode* afterThis, XMLNode* addThis );
void ClearChildren();
void DeleteChild( XMLNode* node );
virtual bool Accept( XMLVisitor* visitor ) const = 0;
virtual void Print( XMLStreamer* streamer );
// fixme: guarentee null terminator to avoid internal checks
virtual char* ParseDeep( char* );
void SetTextParent() { isTextParent = true; }
bool IsTextParent() const { return isTextParent; }
virtual bool IsClosingElement() const { return false; }
@ -345,8 +411,6 @@ protected:
XMLNode( XMLDocument* );
virtual ~XMLNode();
void ClearChildren();
XMLDocument* document;
XMLNode* parent;
bool isTextParent;
@ -373,6 +437,7 @@ public:
const char* Value() { return value.GetStr(); }
void SetValue( const char* );
virtual bool Accept( XMLVisitor* visitor ) const;
virtual XMLText* ToText() { return this; }
char* ParseDeep( char* );
@ -394,6 +459,7 @@ public:
virtual XMLComment* ToComment() { return this; }
const char* Value() { return value.GetStr(); }
virtual bool Accept( XMLVisitor* visitor ) const;
char* ParseDeep( char* );
@ -405,7 +471,7 @@ private:
};
class XMLAttribute : public XMLBase
class XMLAttribute
{
friend class XMLElement;
public:
@ -434,6 +500,7 @@ public:
virtual void Print( XMLStreamer* );
virtual XMLElement* ToElement() { return this; }
virtual bool Accept( XMLVisitor* visitor ) const;
// internal:
virtual bool IsClosingElement() const { return closing; }
@ -459,11 +526,14 @@ public:
XMLDocument();
~XMLDocument();
virtual XMLDocument* ToDocument() { return this; }
int Parse( const char* );
int Load( const char* );
int Load( FILE* );
void Print( XMLStreamer* streamer=0 );
virtual bool Accept( XMLVisitor* visitor ) const;
XMLElement* NewElement( const char* name );
@ -500,13 +570,13 @@ private:
};
class XMLStreamer
class XMLStreamer
{
public:
XMLStreamer( FILE* file );
~XMLStreamer() {}
void OpenElement( const char* name, bool textParent );
void OpenElement( const char* name );
void PushAttribute( const char* name, const char* value );
void CloseElement();
@ -517,24 +587,26 @@ private:
void SealElement();
void PrintSpace( int depth );
void PrintString( const char* ); // prints out, after detecting entities.
bool TextOnStack() const {
/* bool TextOnStack() const {
for( int i=0; i<text.Size(); ++i ) {
if ( text[i] == 'T' )
return true;
}
return false;
}
}*/
FILE* fp;
int depth;
bool elementJustOpened;
int textDepth;
enum {
ENTITY_RANGE = 64
};
bool entityFlag[ENTITY_RANGE];
DynArray< const char*, 10 > stack;
DynArray< char, 10 > text;
//DynArray< char, 10 > text;
};

View File

@ -52,6 +52,7 @@ int main( int argc, const char* argv )
printf( "----------------------------------------------\n" );
}
}
#if 0
{
static const char* test = "<element>Text before.</element>";
XMLDocument doc;
@ -67,5 +68,6 @@ int main( int argc, const char* argv )
doc->Parse( test );
delete doc;
}
#endif
return 0;
}