From 56bdd0259e7f88bd0d901f24cbe9dd33216cf569 Mon Sep 17 00:00:00 2001 From: Lee Thomason Date: Thu, 9 Feb 2012 18:16:58 -0800 Subject: [PATCH] improved the streamer interface so it doesn't require text parent. now possible to connect visitor and streamer. --- tinyxml2.cpp | 140 ++++++++++++++++++++++++++++++++++++--------------- tinyxml2.h | 138 ++++++++++++++++++++++++++++++++++++++------------ xmltest.cpp | 2 + 3 files changed, 206 insertions(+), 74 deletions(-) diff --git a/tinyxml2.cpp b/tinyxml2.cpp index fd11c27..72f9db8 100644 --- a/tinyxml2.cpp +++ b/tinyxml2.cpp @@ -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 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, "", 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(); } diff --git a/tinyxml2.h b/tinyxml2.h index 2a6e344..cfe297f 100644 --- a/tinyxml2.h +++ b/tinyxml2.h @@ -12,7 +12,7 @@ - make constructors protected - hide copy constructor - hide = operator - - UTF8 support: isAlpha, etc. + X UTF8 support: isAlpha, etc. */ #include @@ -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, no children of this node or its sibilings 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(const_cast(this)->FirstChildElement( value )); } - XMLNode* FirstChild() { return firstChild; } - XMLElement* FirstChildElement( const char* value=0 ); + const XMLNode* LastChild() const { return lastChild; } + XMLNode* LastChild() { return const_cast(const_cast(this)->LastChild() ); } + + const XMLElement* LastChildElement( const char* value=0 ) const; + XMLElement* LastChildElement( const char* value=0 ) { return const_cast(const_cast(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(const_cast(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(const_cast(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 stack; - DynArray< char, 10 > text; + //DynArray< char, 10 > text; }; diff --git a/xmltest.cpp b/xmltest.cpp index 243f0fa..7be7c2e 100644 --- a/xmltest.cpp +++ b/xmltest.cpp @@ -52,6 +52,7 @@ int main( int argc, const char* argv ) printf( "----------------------------------------------\n" ); } } +#if 0 { static const char* test = "Text before."; XMLDocument doc; @@ -67,5 +68,6 @@ int main( int argc, const char* argv ) doc->Parse( test ); delete doc; } +#endif return 0; } \ No newline at end of file