Mbed port of the Simple Plain Xml parser. See http://code.google.com/p/spxml/ for more details. This library uses less memory and is much better suited to streaming data than TinyXML (doesn\'t use as much C++ features, and especially works without streams). See http://mbed.org/users/hlipka/notebook/xml-parsing/ for usage examples.

Dependents:   spxmltest_weather VFD_fontx2_weather weather_LCD_display News_LCD_display ... more

Files at this revision

API Documentation at this revision

Comitter:
hlipka
Date:
Wed Nov 24 20:52:14 2010 +0000
Commit message:
initial revision

Changed in this revision

mstring.cpp Show annotated file Show diff for this revision Revisions of this file
mstring.h Show annotated file Show diff for this revision Revisions of this file
spcanonxml.cpp Show annotated file Show diff for this revision Revisions of this file
spcanonxml.hpp Show annotated file Show diff for this revision Revisions of this file
spdomiterator.cpp Show annotated file Show diff for this revision Revisions of this file
spdomiterator.hpp Show annotated file Show diff for this revision Revisions of this file
spdomparser.cpp Show annotated file Show diff for this revision Revisions of this file
spdomparser.hpp Show annotated file Show diff for this revision Revisions of this file
spxmlcodec.cpp Show annotated file Show diff for this revision Revisions of this file
spxmlcodec.hpp Show annotated file Show diff for this revision Revisions of this file
spxmlevent.cpp Show annotated file Show diff for this revision Revisions of this file
spxmlevent.hpp Show annotated file Show diff for this revision Revisions of this file
spxmlhandle.cpp Show annotated file Show diff for this revision Revisions of this file
spxmlhandle.hpp Show annotated file Show diff for this revision Revisions of this file
spxmlnode.cpp Show annotated file Show diff for this revision Revisions of this file
spxmlnode.hpp Show annotated file Show diff for this revision Revisions of this file
spxmlparser.cpp Show annotated file Show diff for this revision Revisions of this file
spxmlparser.hpp Show annotated file Show diff for this revision Revisions of this file
spxmlreader.cpp Show annotated file Show diff for this revision Revisions of this file
spxmlreader.hpp Show annotated file Show diff for this revision Revisions of this file
spxmlstag.cpp Show annotated file Show diff for this revision Revisions of this file
spxmlstag.hpp Show annotated file Show diff for this revision Revisions of this file
spxmlutils.cpp Show annotated file Show diff for this revision Revisions of this file
spxmlutils.hpp Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mstring.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,35 @@
+/*
+* Copyright (c) 2010 Hendrik Lipka
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+/*
+    implementation for the missing strdup function
+*/
+
+#include <string.h>
+#include <stdlib.h>
+
+char* strdup (const char *str) {
+    char *result = (char*)(malloc (strlen (str) + 1));
+    if (result != NULL)
+        strcpy (result,str);
+    return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mstring.h	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,28 @@
+/*
+* Copyright (c) 2010 Hendrik Lipka
+* 
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+* 
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+* 
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+#ifndef MSTRING_H
+#define MSTRING_H
+
+char *strdup (const char *s);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spcanonxml.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <string.h>
+
+#include "spcanonxml.hpp"
+
+#include "spxmlnode.hpp"
+#include "spxmlutils.hpp"
+#include "spxmlcodec.hpp"
+
+SP_CanonXmlBuffer :: SP_CanonXmlBuffer( const SP_XmlNode * node )
+{
+    mBuffer = new SP_XmlStringBuffer();
+    dump( node, mBuffer );
+}
+
+SP_CanonXmlBuffer :: ~SP_CanonXmlBuffer()
+{
+    if( NULL != mBuffer ) delete mBuffer;
+    mBuffer = NULL;
+}
+
+const char * SP_CanonXmlBuffer :: getBuffer() const
+{
+    return mBuffer->getBuffer();
+}
+
+int SP_CanonXmlBuffer :: getSize() const
+{
+    return mBuffer->getSize();
+}
+
+void SP_CanonXmlBuffer :: canonEncode( const char * value,
+        SP_XmlStringBuffer * buffer )
+{
+    SP_XmlStringBuffer temp;
+    SP_XmlStringCodec::encode( "", value, &temp );
+
+    for( const char * pos = temp.getBuffer(); '\0' != *pos; pos++ ) {
+        if( '\r' == *pos ) {
+        } else if( '\n' == *pos ) {
+            buffer->append( "&#10;" );
+        } else {
+            buffer->append( *pos );
+        }
+    }
+}
+
+void SP_CanonXmlBuffer :: dump(
+        const SP_XmlNode * node, SP_XmlStringBuffer * buffer )
+{
+    if( NULL == node ) return;
+
+    if( SP_XmlNode::eXMLDOC == node->getType() ) {
+        SP_XmlDocument * document = static_cast<SP_XmlDocument*>((SP_XmlNode*)node);
+        const SP_XmlNodeList * children = document->getChildren();
+        for( int j = 0; j < children->getLength(); j++ ) {
+            dump( children->get( j ), buffer );
+        }
+    } else if( SP_XmlNode::eCDATA == node->getType() ) {
+        SP_XmlCDataNode * cdata = static_cast<SP_XmlCDataNode*>((SP_XmlNode*)node);
+
+        canonEncode( cdata->getText(), buffer );
+    } else if( SP_XmlNode::ePI == node->getType() ) {
+        SP_XmlPINode * piNode = static_cast<SP_XmlPINode*>((SP_XmlNode*)node);
+
+        buffer->append( "<?" );
+        buffer->append( piNode->getTarget() );
+        if( '\0' != *( piNode->getTarget() ) ) buffer->append( ' ' );
+        buffer->append( piNode->getData() );
+        buffer->append( "?>" );
+    } else if( SP_XmlNode::eCOMMENT == node->getType() ) {
+        // ignore
+    } else if( SP_XmlNode::eELEMENT == node->getType() ) {
+        dumpElement( node, buffer );
+    } else if( SP_XmlNode::eDOCDECL == node->getType() ) {
+        // ignore
+    } else if( SP_XmlNode::eDOCTYPE == node->getType() ) {
+        // ignore
+    } else {
+        // ignore
+    }
+}
+
+void SP_CanonXmlBuffer :: dumpElement(
+        const SP_XmlNode * node, SP_XmlStringBuffer * buffer )
+{
+    if( NULL == node ) return;
+
+    if( SP_XmlNode::eELEMENT == node->getType() ) {
+        SP_XmlElementNode * element = static_cast<SP_XmlElementNode*>((SP_XmlNode*)node);
+        buffer->append( "<" );
+        buffer->append( element->getName() );
+
+        int i = 0;
+
+        SP_XmlArrayList attrList;
+        for( i = 0; i < element->getAttrCount(); i++ ) {
+            attrList.append( (void*)element->getAttr( i, NULL ) );
+        }
+        attrList.sort( reinterpret_cast<int(*)(const void*, const void*)>(strcmp) );
+
+        const char * name = NULL, * value = NULL;
+        for( i = 0; i < attrList.getCount(); i++ ) {
+            name = (char*)attrList.getItem( i );
+            value = element->getAttrValue( name );
+            if( NULL != name && NULL != value ) {
+                buffer->append( ' ' );
+                buffer->append( name );
+                buffer->append( "=\"" );
+                canonEncode( value, buffer );
+                buffer->append( "\"" );
+            }
+        }
+
+        const SP_XmlNodeList * children = element->getChildren();
+
+        buffer->append( ">" );
+
+        for( int j = 0; j < children->getLength(); j++ ) {
+            dump( children->get( j ), buffer );
+        }
+
+        buffer->append( "</" );
+        buffer->append( element->getName() );
+        buffer->append( ">" );
+    } else {
+        dump( node, buffer );
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spcanonxml.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __spcanonxml_hpp__
+#define __spcanonxml_hpp__
+
+class SP_XmlNode;
+class SP_XmlStringBuffer;
+class SP_XmlDocDeclNode;
+class SP_XmlDocTypeNode;
+
+/// XML Canonical, defined by James Clark.
+class SP_CanonXmlBuffer {
+public:
+    SP_CanonXmlBuffer( const SP_XmlNode * node );
+    ~SP_CanonXmlBuffer();
+
+    const char * getBuffer() const;
+    int getSize() const;
+
+private:
+    SP_CanonXmlBuffer( SP_CanonXmlBuffer & );
+    SP_CanonXmlBuffer & operator=( SP_CanonXmlBuffer & );
+
+    static void dump( const SP_XmlNode * node,
+            SP_XmlStringBuffer * buffer );
+    static void dumpElement( const SP_XmlNode * node,
+            SP_XmlStringBuffer * buffer );
+
+    static void canonEncode( const char * value,
+            SP_XmlStringBuffer * buffer );
+
+    SP_XmlStringBuffer * mBuffer;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spdomiterator.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "spdomiterator.hpp"
+#include "spxmlnode.hpp"
+
+SP_DomIterator :: SP_DomIterator( const SP_XmlNode * node )
+{
+    mRoot = node;
+    mCurrent = node;
+}
+
+SP_DomIterator :: ~SP_DomIterator()
+{
+}
+
+const SP_XmlNode * SP_DomIterator :: getNext()
+{
+    if( NULL == mCurrent ) return NULL;
+
+    const SP_XmlNode * retNode = mCurrent;
+
+    // find first left child
+    if( SP_XmlNode::eXMLDOC == mCurrent->getType() ) {
+        SP_XmlDocument * document = static_cast<SP_XmlDocument*>((SP_XmlNode*)mCurrent);
+
+        mCurrent = NULL;
+        retNode = document->getDocDecl();
+        if( NULL == retNode ) retNode = document->getDocType();
+        if( NULL == retNode ) {
+            retNode = document->getRootElement();
+            if( NULL != retNode ) mCurrent = document->getRootElement()->getChildren()->get( 0 );
+        }
+    } else if( SP_XmlNode::eELEMENT == mCurrent->getType() ) {
+        SP_XmlElementNode * element = static_cast<SP_XmlElementNode*>((SP_XmlNode*)mCurrent);
+        const SP_XmlNodeList * children = element->getChildren();
+        mCurrent = children->get( 0 );
+    } else {
+        mCurrent = NULL;
+    }
+
+    // find next sibling
+    if( NULL == mCurrent ) {
+        mCurrent = retNode;
+
+        const SP_XmlNode * parent = NULL;
+        if( NULL != mCurrent ) parent = mCurrent->getParent();
+
+        for( ; NULL != parent; ) {
+            if( SP_XmlNode::eXMLDOC == parent->getType() ) {
+                SP_XmlDocument * document = static_cast<SP_XmlDocument*>((SP_XmlNode*)parent);
+                if( mCurrent == document->getDocDecl() ) {
+                    mCurrent = document->getDocType();
+                    if( NULL == mCurrent ) mCurrent = document->getRootElement();
+                } else if( mCurrent == document->getDocType() ) {
+                    mCurrent = document->getRootElement();
+                } else {
+                    mCurrent = NULL;
+                }
+            } else if( SP_XmlNode::eELEMENT == parent->getType() ) {
+                SP_XmlElementNode * element = static_cast<SP_XmlElementNode*>((SP_XmlNode*)parent);
+                const SP_XmlNodeList * children = element->getChildren();
+
+                int index = -1;
+                for( int i = 0; i < children->getLength(); i++ ) {
+                    if( mCurrent == children->get( i ) ) {
+                        index = i;
+                        break;
+                    }
+                }
+
+                if( index >= 0 && index < ( children->getLength() - 1 ) ) {
+                    mCurrent = children->get( index + 1 );
+                } else {
+                    mCurrent = NULL;
+                }
+            } else {
+                mCurrent = NULL;
+                assert( 0 ); // should not occur
+            }
+
+            if( NULL == mCurrent ) {
+                mCurrent = parent;
+                parent = mCurrent->getParent();
+                if( NULL == parent ) mCurrent = NULL;
+                if( mRoot == mCurrent ) mCurrent = NULL;
+            } else {
+                break;
+            }
+        }
+    }
+
+    return retNode;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spdomiterator.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __spdomiterator_hpp__
+#define __spdomiterator_hpp__
+
+class SP_XmlNode;
+
+/// DFS iterator -- Depth First Search
+class SP_DomIterator {
+public:
+    /// node as tree node, iterator this tree by DFS
+    SP_DomIterator( const SP_XmlNode * node );
+    ~SP_DomIterator();
+
+    /// @return NULL : reach the end
+    const SP_XmlNode * getNext();
+
+private:
+
+    SP_DomIterator( SP_DomIterator & );
+    SP_DomIterator & operator=( SP_DomIterator & );
+
+    const SP_XmlNode * mRoot;
+    const SP_XmlNode * mCurrent;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spdomparser.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <assert.h>
+
+#include "spdomparser.hpp"
+#include "spxmlparser.hpp"
+#include "spxmlevent.hpp"
+#include "spxmlutils.hpp"
+#include "spxmlnode.hpp"
+#include "spxmlcodec.hpp"
+
+//=========================================================
+
+SP_XmlDomParser :: SP_XmlDomParser()
+{
+    mParser = new SP_XmlPullParser();
+    mDocument = new SP_XmlDocument();
+    mCurrent = NULL;
+}
+
+SP_XmlDomParser :: ~SP_XmlDomParser()
+{
+    if( NULL != mDocument ) delete mDocument;
+    mDocument = NULL;
+
+    if( NULL != mParser ) delete mParser;
+    mParser = NULL;
+}
+
+void SP_XmlDomParser :: setIgnoreWhitespace( int ignoreWhitespace )
+{
+    mParser->setIgnoreWhitespace( ignoreWhitespace );
+}
+
+int SP_XmlDomParser :: getIgnoreWhitespace()
+{
+    return mParser->getIgnoreWhitespace();
+}
+
+const char * SP_XmlDomParser :: getEncoding()
+{
+    return mParser->getEncoding();
+}
+
+int SP_XmlDomParser :: append( const char * source, int len )
+{
+    int ret = 0;
+
+    for( int pos = 0; pos < len; pos += 64 ) {
+        int realLen = ( len - pos ) > 64 ? 64 : ( len - pos );
+        ret += mParser->append( source + pos, realLen );
+        buildTree();
+    }
+
+    return ret;
+}
+
+void SP_XmlDomParser :: buildTree()
+{
+    for( SP_XmlPullEvent * event = mParser->getNext();
+            NULL != event; event = mParser->getNext() ) {
+
+        switch( event->getEventType() ) {
+            case SP_XmlPullEvent::eStartDocument:
+                // ignore
+                delete event;
+                break;
+            case SP_XmlPullEvent::eEndDocument:
+                // ignore
+                delete event;
+                break;
+            case SP_XmlPullEvent::eDocDecl:
+                {
+                    mDocument->setDocDecl(
+                            new SP_XmlDocDeclNode( (SP_XmlDocDeclEvent*)event ) );
+                    break;
+                }
+            case SP_XmlPullEvent::eDocType:
+                {
+                    mDocument->setDocType(
+                            new SP_XmlDocTypeNode( (SP_XmlDocTypeEvent*)event ) );
+                    break;
+                }
+            case SP_XmlPullEvent::eStartTag:
+                {
+                    SP_XmlElementNode * element =
+                            new SP_XmlElementNode( (SP_XmlStartTagEvent*)event );
+                    if( NULL == mCurrent ) {
+                        mCurrent = element;
+                        mDocument->setRootElement( element );
+                    } else {
+                        mCurrent->addChild( element );
+                        mCurrent = element;
+                    }
+                    break;
+                }
+            case SP_XmlPullEvent::eEndTag:
+                {
+                    SP_XmlNode * parent = (SP_XmlNode*)mCurrent->getParent();
+                    if( NULL != parent && SP_XmlNode::eELEMENT == parent->getType() ) {
+                        mCurrent = static_cast<SP_XmlElementNode*>((SP_XmlNode*)parent);
+                    } else {
+                        mCurrent = NULL;
+                    }
+
+                    delete event;
+                    break;
+                }
+            case SP_XmlPullEvent::eCData:
+                {
+                    if( NULL != mCurrent ) {
+                        mCurrent->addChild( new SP_XmlCDataNode( (SP_XmlCDataEvent*)event ) );
+                    } else {
+                        delete event;
+                    }
+                    break;
+                }
+            case SP_XmlPullEvent::eComment:
+                {
+                    if( NULL != mCurrent ) {
+                        mCurrent->addChild( new SP_XmlCommentNode( (SP_XmlCommentEvent*)event ) );
+                    } else {
+                        delete event;
+                    }
+                    break;
+                }
+            case SP_XmlPIEvent::ePI:
+                {
+                    if( NULL != mCurrent ) {
+                        mCurrent->addChild( new SP_XmlPINode( (SP_XmlPIEvent*)event ) );
+                    } else {
+                        mDocument->getChildren()->append(
+                                new SP_XmlPINode( (SP_XmlPIEvent*)event ) );
+                    }
+                    break;
+                }
+            default:
+                {
+                    assert( 0 );
+                    break;
+                }
+        }
+    }
+}
+
+const char * SP_XmlDomParser :: getError()
+{
+    return mParser->getError();
+}
+
+const SP_XmlDocument * SP_XmlDomParser :: getDocument() const
+{
+    return mDocument;
+}
+
+//=========================================================
+
+SP_XmlDomBuffer :: SP_XmlDomBuffer( const SP_XmlNode * node, int indent )
+{
+    mBuffer = new SP_XmlStringBuffer();
+    dump( SP_XmlStringCodec::DEFAULT_ENCODING, node, mBuffer, indent ? 0 : -1 );
+}
+
+SP_XmlDomBuffer :: SP_XmlDomBuffer( const char * encoding, const SP_XmlNode * node, int indent )
+{
+    mBuffer = new SP_XmlStringBuffer();
+    dump( encoding, node, mBuffer, indent ? 0 : -1 );
+}
+
+SP_XmlDomBuffer :: ~SP_XmlDomBuffer()
+{
+    if( NULL != mBuffer ) delete mBuffer;
+    mBuffer = NULL;
+}
+
+const char * SP_XmlDomBuffer :: getBuffer() const
+{
+    return mBuffer->getBuffer();
+}
+
+int SP_XmlDomBuffer :: getSize() const
+{
+    return mBuffer->getSize();
+}
+
+void SP_XmlDomBuffer :: dump( const char * encoding,
+        const SP_XmlNode * node, SP_XmlStringBuffer * buffer, int level )
+{
+    if( SP_XmlNode::eXMLDOC == node->getType() ) {
+        SP_XmlDocument * document = static_cast<SP_XmlDocument*>((SP_XmlNode*)node);
+        dumpDocDecl( encoding, document->getDocDecl(), buffer, level );
+        dumpDocType( encoding, document->getDocType(), buffer, level );
+
+        const SP_XmlNodeList * children = document->getChildren();
+        for( int j = 0; j < children->getLength(); j++ ) {
+            dump( encoding, children->get( j ), buffer, level );
+        }
+    } else if( SP_XmlNode::eCDATA == node->getType() ) {
+        SP_XmlCDataNode * cdata = static_cast<SP_XmlCDataNode*>((SP_XmlNode*)node);
+        SP_XmlStringCodec::encode( encoding, cdata->getText(), buffer );
+    } else if( SP_XmlNode::eCOMMENT == node->getType() ) {
+        SP_XmlCommentNode * comment = static_cast<SP_XmlCommentNode*>((SP_XmlNode*)node);
+
+        if( level >= 0 ) {
+            buffer->append( '\n' );
+            for( int i = 0; i < level; i++ ) buffer->append( '\t' );
+            buffer->append( "<!--" );
+            buffer->append( comment->getText() );
+            buffer->append( "-->\n" );
+        } else {
+            buffer->append( "<!--" );
+            buffer->append( comment->getText() );
+            buffer->append( "-->" );
+        }
+    } else if( SP_XmlNode::eELEMENT == node->getType() ) {
+        dumpElement( encoding, node, buffer, level );
+    } else if( SP_XmlNode::eDOCDECL == node->getType() ) {
+        dumpDocDecl( encoding, (SP_XmlDocDeclNode*)node, buffer, level );
+    } else if( SP_XmlNode::eDOCTYPE == node->getType() ) {
+        dumpDocType( encoding, (SP_XmlDocTypeNode*)node, buffer, level );
+    } else if( SP_XmlNode::ePI == node->getType() ) {
+        SP_XmlPINode * piNode = static_cast<SP_XmlPINode*>((SP_XmlNode*)node);
+
+        if( level >= 0 ) {
+            for( int i = 0; i < level; i++ ) buffer->append( '\t' );
+            buffer->append( "<?" );
+            buffer->append( piNode->getTarget() );
+            buffer->append( ' ' );
+            buffer->append( piNode->getData() );
+            buffer->append( "?>\n" );
+        } else {
+            buffer->append( "<?" );
+            buffer->append( piNode->getTarget() );
+            if( '\0' != *( piNode->getTarget() ) ) buffer->append( ' ' );
+            buffer->append( piNode->getData() );
+            buffer->append( "?>" );
+        }
+    } else {
+        // ignore
+    }
+}
+
+void SP_XmlDomBuffer :: dumpDocDecl( const char * encoding,
+        const SP_XmlDocDeclNode * docDecl,
+        SP_XmlStringBuffer * buffer, int level )
+{
+    if( NULL == docDecl ) return;
+
+    buffer->append( "<?xml version=\"" );
+    if( '\0' != * ( docDecl->getVersion() ) ) {
+        buffer->append( docDecl->getVersion() );
+    } else {
+        buffer->append( "1.0" );
+    }
+    buffer->append( "\" " );
+
+    if( '\0' != * ( docDecl->getEncoding() ) ) {
+        buffer->append( "encoding=\"" );
+        buffer->append( docDecl->getEncoding() );
+        buffer->append( "\" " );
+    }
+
+    if( -1 != docDecl->getStandalone() ) {
+        char standalone[ 32 ];
+        snprintf( standalone, sizeof( standalone ), "standalone=\"%s\" ",
+                0 == docDecl->getStandalone() ? "no" : "yes" );
+        buffer->append( standalone );
+    }
+
+    buffer->append( level >= 0 ? "?>\n" : "?>" );
+}
+
+void SP_XmlDomBuffer :: dumpDocType( const char * encoding,
+        const SP_XmlDocTypeNode * docType,
+        SP_XmlStringBuffer * buffer, int level )
+{
+    if( NULL == docType ) return;
+
+    buffer->append( "<!DOCTYPE " );
+    buffer->append( docType->getName() );
+
+    if( '\0' != * ( docType->getPublicID() ) ) {
+        buffer->append( " PUBLIC " );
+        buffer->append( '"' );
+        buffer->append( docType->getPublicID() );
+        buffer->append( '"' );
+    }
+
+    if( '\0' != * ( docType->getSystemID() ) ) {
+        buffer->append( " SYSTEM " );
+        buffer->append( '"' );
+        buffer->append( docType->getSystemID() );
+        buffer->append( '"' );
+    }
+
+    if( '\0' != * ( docType->getDTD() ) ) {
+        buffer->append( " \"" );
+        buffer->append( docType->getDTD() );
+        buffer->append( '"' );
+    }
+
+    buffer->append( level >= 0 ? ">\n" : ">" );
+}
+
+void SP_XmlDomBuffer :: dumpElement( const char * encoding,
+        const SP_XmlNode * node, SP_XmlStringBuffer * buffer, int level )
+{
+    if( NULL == node ) return;
+
+    if( SP_XmlNode::eELEMENT == node->getType() ) {
+        int i = 0;
+
+        for( i = 0; i < level; i++ ) buffer->append( '\t' );
+
+        SP_XmlElementNode * element = static_cast<SP_XmlElementNode*>((SP_XmlNode*)node);
+        buffer->append( "<" );
+        buffer->append( element->getName() );
+
+        const char * name = NULL, * value = NULL;
+        for( i = 0; i < element->getAttrCount(); i++ ) {
+            name = element->getAttr( i, &value );
+            if( NULL != name && NULL != value ) {
+                buffer->append( ' ' );
+                buffer->append( name );
+                buffer->append( "=\"" );
+                SP_XmlStringCodec::encode( encoding, value, buffer );
+                buffer->append( "\"" );
+            }
+        }
+
+        const SP_XmlNodeList * children = element->getChildren();
+
+        if( children->getLength() > 0 ) {
+            if( SP_XmlNode::eCDATA != children->get( 0 )->getType() ) {
+                buffer->append( level >= 0 ? ">\n" : ">" );
+            } else {
+                buffer->append( ">" );
+            }
+
+            for( int j = 0; j < children->getLength(); j++ ) {
+                dump( encoding, children->get( j ), buffer, level >= 0 ? level + 1 : -1 );
+            }
+
+            if( SP_XmlNode::eCDATA != children->get( 0 )->getType() ) {
+                for( int i = 0; i < level; i++ ) buffer->append( '\t' );
+            }
+            buffer->append( "</" );
+            buffer->append( element->getName() );
+            buffer->append( level >= 0 ? ">\n" : ">" );
+        } else {
+            buffer->append( level >= 0 ? "/>\n" : ">" );
+        }
+    } else {
+        dump( encoding, node, buffer, level );
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spdomparser.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __spdomparser_hpp__
+#define __spdomparser_hpp__
+
+class SP_XmlNode;
+class SP_XmlDocument;
+class SP_XmlElementNode;
+class SP_XmlDocDeclNode;
+class SP_XmlDocTypeNode;
+class SP_XmlPullParser;
+class SP_XmlStringBuffer;
+
+/// parse string to xml node tree
+class SP_XmlDomParser {
+public:
+    SP_XmlDomParser();
+    ~SP_XmlDomParser();
+
+    /// append more input xml source
+    /// @return how much byte has been consumed
+    int append( const char * source, int len );
+
+    /// @return NOT NULL : the detail error message
+    /// @return NULL : no error
+    const char * getError();
+    
+    /// get the parse result
+    const SP_XmlDocument * getDocument() const;
+
+    void setIgnoreWhitespace( int ignoreWhitespace );
+
+    int getIgnoreWhitespace();
+
+    const char * getEncoding();
+
+private:
+    void buildTree();
+
+    SP_XmlDomParser( SP_XmlDomParser & );
+    SP_XmlDomParser & operator=( SP_XmlDomParser & );
+
+    SP_XmlPullParser * mParser;
+    SP_XmlDocument * mDocument;
+    SP_XmlElementNode * mCurrent;
+};
+
+/// serialize xml node tree to string
+class SP_XmlDomBuffer {
+public:
+    SP_XmlDomBuffer( const SP_XmlNode * node, int indent = 1 );
+    SP_XmlDomBuffer( const char * encoding, const SP_XmlNode * node, int indent = 1 );
+    ~SP_XmlDomBuffer();
+
+    const char * getBuffer() const;
+    int getSize() const;
+
+private:
+    SP_XmlDomBuffer( SP_XmlDomBuffer & );
+    SP_XmlDomBuffer & operator=( SP_XmlDomBuffer & );
+
+    static void dumpDocDecl( const char * encoding,
+            const SP_XmlDocDeclNode * docDecl,
+            SP_XmlStringBuffer * buffer, int level );
+    static void dumpDocType( const char * encoding,
+            const SP_XmlDocTypeNode * docType,
+            SP_XmlStringBuffer * buffer, int level );
+    static void dump( const char * encoding,
+            const SP_XmlNode * node,
+            SP_XmlStringBuffer * buffer, int level );
+    static void dumpElement( const char * encoding,
+            const SP_XmlNode * node,
+            SP_XmlStringBuffer * buffer, int level );
+
+    SP_XmlStringBuffer * mBuffer;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlcodec.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "spxmlcodec.hpp"
+#include "spxmlutils.hpp"
+
+const char * SP_XmlStringCodec :: DEFAULT_ENCODING = "utf-8";
+
+const char SP_XmlStringCodec :: XML_CHARS [] =
+        { '<', '>', '&', '\'', '"' };
+const char * SP_XmlStringCodec :: ESC_CHARS [] =
+        { "&lt;", "&gt;", "&amp;", "&apos;", "&quot;" };
+
+int SP_XmlStringCodec :: decode( const char * encoding, const char * encodeValue,
+        SP_XmlStringBuffer * outBuffer )
+{
+    int isUtf8 = ( 0 == strcasecmp( encoding, "utf-8" ) );
+
+    const char * pos = encodeValue;
+    for( ; '\0' != *pos; ) {
+        if( '&' == *pos ) {
+            int index = -1;
+            int len = 0;
+            for( int i = 0; i < (int)( sizeof( ESC_CHARS ) / sizeof( ESC_CHARS[0] ) ); i++ ) {
+                len = strlen( ESC_CHARS[ i ] );
+                if( 0 == strncmp( pos, ESC_CHARS[i], len ) ) {
+                    index = i;
+                    break;
+                }
+            }
+            if( index >= 0 ) {
+                outBuffer->append( XML_CHARS[ index ] );
+                pos += len;
+            } else {
+                char * next = "";
+                int ch = 0;
+                if( '#' == *( pos + 1 ) ) {
+                    if( 'x' == *( pos + 2 ) ) {
+                        ch = strtol( pos + 3, &next, 16 );
+                    } else {
+                        ch = strtol( pos + 2, &next, 10 );
+                    }
+                }
+
+                // TODO: fully support xml entity, currently only support unicode entity
+                if( ';' == *next && 0 != ch ) {
+                    if( isUtf8 ) {
+                        SP_XmlUtf8Codec::uni2utf8( ch, outBuffer );
+                    } else {
+                        outBuffer->append( ch );
+                    }
+                    pos = next + 1;
+                } else {
+                    outBuffer->append( *pos++ );
+                }
+            }
+        } else {
+            outBuffer->append( *pos++ );
+        }
+    }
+
+    return 0;
+}
+
+int SP_XmlStringCodec :: encode( const char * encoding, const char * decodeValue,
+        SP_XmlStringBuffer * outBuffer )
+{
+    int isUtf8 = ( 0 == strcasecmp( encoding, "utf-8" ) );
+
+    const unsigned char * pos = (unsigned char *)decodeValue;
+    for( ; '\0' != *pos; pos++ ) {
+        int index = -1;
+        for( int i = 0; i < (int)( sizeof( XML_CHARS ) / sizeof( XML_CHARS[0] ) ); i++ ) {
+            if( XML_CHARS[i] == *pos ) {
+                index = i;
+                break;
+            }
+        }
+        if( index >= 0 && '\'' != *pos ) {
+            outBuffer->append( ESC_CHARS[ index ] );
+        } else {
+            if( isUtf8 ) {
+                int ch = 0;
+                int len = SP_XmlUtf8Codec::utf82uni( (unsigned char*)pos, &ch );
+
+                if( len > 0 ) {
+                    pos += len - 1;
+
+                    char temp[ 32 ] = { 0 };
+                    snprintf( temp, sizeof( temp ), "&#%d;", ch );
+                    outBuffer->append( temp );
+                } else {
+                    outBuffer->append( *pos );
+                }
+            } else {
+                if( *pos < 32 ) {
+                    char temp[ 32 ] = { 0 };
+                    snprintf( temp, sizeof( temp ), "&#%d;", *pos );
+                    outBuffer->append( temp );
+                } else {
+                    outBuffer->append( *pos );
+                }
+            }
+        }    
+    }
+
+    return 0;
+}
+
+int SP_XmlStringCodec :: isNameChar( const char * encoding, char c )
+{
+    if( 0 == strcasecmp( encoding, "utf-8" ) ) {
+        return 1;
+    } else {
+        return isalnum(c) || c == ':' || c == '-' || c == '.' || c == '_';
+    }
+}
+
+//=========================================================
+
+int SP_XmlUtf8Codec :: utf82uni( const unsigned char * utf8, int * ch )
+{
+    int len = 0;
+
+    unsigned char c1 = 0, c2 = 0, c3 = 0, c4 = 0;
+
+    if( *utf8 >= 0x80 ) {
+        c1 = *utf8++;
+
+        if( c1 < 0xE0 ) {         // 2 bytes
+            if( '\0' != ( c2 = *utf8 ) ) {
+                *ch = ((c1 & 0x1F) << 6) | (c2 & 0x3F);
+                len = 2;
+            }
+        } else if( c1 < 0xF0 ) {  // 3 bytes
+            if( '\0' != ( c2 = *utf8++ ) && '\0' != ( c3 = *utf8 ) ) {
+                *ch = ((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6)| (c3 & 0x3F);
+                len = 3;
+            }
+        } else {                     // 4 bytes
+            if( '\0' != ( c2 = *utf8++ ) && '\0' != ( c3 = *utf8++ )
+                    && '\0' != ( c4 = *utf8 ) ) {
+                *ch = ((c1 & 0x07) << 16) | ((c2 & 0x3F) << 12)
+                        | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
+                len = 4;
+            }
+        }
+    }
+
+    return len;
+}
+
+void SP_XmlUtf8Codec :: uni2utf8( int ch, SP_XmlStringBuffer * outBuffer )
+{
+    if( ch < 0x80 ) outBuffer->append( ch );
+    else if( ch < 0x800 ) {
+        outBuffer->append( 0xC0 | ( ch >> 6 ) );
+        outBuffer->append( 0x80 | ( ch & 0x3F ) );
+    } else if( ch < 0x10000 ) {
+        outBuffer->append( 0xE0 | ( ch >> 12 ) );
+        outBuffer->append( 0x80 | ( ( ch >> 6 ) & 0x3F ) );
+        outBuffer->append( 0x80 | ( ch & 0x3F ) );
+    } else if( ch < 0x200000 ) {
+        outBuffer->append( 0xF0 | ( ch >> 18 ) );
+        outBuffer->append( 0x80 | ( ( ch >> 12 ) & 0x3F ) );
+        outBuffer->append( 0x80 | ( ( ch >> 6 ) & 0x3F ) );
+        outBuffer->append( 0x80 | ( ch & 0x3F ) );
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlcodec.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __spxmlcodec_hpp__
+#define __spxmlcodec_hpp__
+
+class SP_XmlStringBuffer;
+
+class SP_XmlStringCodec {
+public:
+
+    static const char * DEFAULT_ENCODING;
+
+    static int decode( const char * encoding,
+            const char * encodeValue, SP_XmlStringBuffer * outBuffer );
+    static int encode( const char * encoding,
+            const char * decodeValue, SP_XmlStringBuffer * outBuffer );
+    static int isNameChar( const char * encoding, char c );
+
+private:
+    static const char XML_CHARS [];
+    static const char * ESC_CHARS [];
+
+    SP_XmlStringCodec();
+};
+
+class SP_XmlUtf8Codec {
+public:
+
+    // @return convert how many bytes
+    static int utf82uni( const unsigned char * utf8, int * ch );
+
+    static void uni2utf8( int ch, SP_XmlStringBuffer * outBuffer );
+
+private:
+    SP_XmlUtf8Codec();
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlevent.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,372 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "mstring.h"
+
+#include "spxmlevent.hpp"
+#include "spxmlutils.hpp"
+
+SP_XmlPullEvent :: SP_XmlPullEvent( int eventType )
+    : mEventType( eventType )
+{
+}
+
+SP_XmlPullEvent :: ~SP_XmlPullEvent()
+{
+}
+
+int SP_XmlPullEvent :: getEventType()
+{
+    return mEventType;
+}
+
+//=========================================================
+
+SP_XmlPullEventQueue :: SP_XmlPullEventQueue()
+{
+    mQueue = new SP_XmlQueue();
+}
+
+SP_XmlPullEventQueue :: ~SP_XmlPullEventQueue()
+{
+    for( void * item = mQueue->pop(); NULL != item; item = mQueue->pop() ) {
+        delete (SP_XmlPullEvent*)item;
+    }
+
+    delete mQueue;
+}
+
+void SP_XmlPullEventQueue :: enqueue( SP_XmlPullEvent * event )
+{
+    mQueue->push( event );
+}
+
+SP_XmlPullEvent * SP_XmlPullEventQueue :: dequeue()
+{
+    SP_XmlPullEvent * event = (SP_XmlPullEvent*)mQueue->pop();
+    return event;
+}
+
+//=========================================================
+
+SP_XmlStartDocEvent :: SP_XmlStartDocEvent()
+    : SP_XmlPullEvent( eStartDocument )
+{
+}
+
+SP_XmlStartDocEvent :: ~SP_XmlStartDocEvent()
+{
+}
+
+//=========================================================
+
+SP_XmlEndDocEvent :: SP_XmlEndDocEvent()
+    : SP_XmlPullEvent( eEndDocument )
+{
+}
+
+SP_XmlEndDocEvent :: ~SP_XmlEndDocEvent()
+{
+}
+
+//=========================================================
+
+SP_XmlDocTypeEvent :: SP_XmlDocTypeEvent()
+    : SP_XmlPullEvent( eDocType )
+{
+    memset( mName, 0, sizeof( mName ) );
+    memset( mSystemID, 0, sizeof( mSystemID ) );
+    memset( mPublicID, 0, sizeof( mPublicID ) );
+    memset( mDTD, 0, sizeof( mDTD ) );
+}
+
+SP_XmlDocTypeEvent :: ~SP_XmlDocTypeEvent()
+{
+}
+
+void SP_XmlDocTypeEvent :: setName( const char * name )
+{
+    strncpy( mName, name, sizeof( mName ) - 1 );
+}
+
+const char * SP_XmlDocTypeEvent :: getName() const
+{
+    return mName;
+}
+
+void SP_XmlDocTypeEvent :: setSystemID( const char * systemID )
+{
+    strncpy( mSystemID, systemID, sizeof( mSystemID ) - 1 );
+}
+
+const char * SP_XmlDocTypeEvent :: getSystemID() const
+{
+    return mSystemID;
+}
+
+void SP_XmlDocTypeEvent :: setPublicID( const char * publicID )
+{
+    strncpy( mPublicID, publicID, sizeof( mPublicID ) - 1 );
+}
+
+const char * SP_XmlDocTypeEvent :: getPublicID() const
+{
+    return mPublicID;
+}
+
+void SP_XmlDocTypeEvent :: setDTD( const char * dtd )
+{
+    strncpy( mDTD, dtd, sizeof( mDTD ) - 1 );
+}
+
+const char * SP_XmlDocTypeEvent :: getDTD() const
+{
+    return mDTD;
+}
+
+//=========================================================
+
+SP_XmlPIEvent :: SP_XmlPIEvent()
+    : SP_XmlPullEvent( ePI )
+{
+    memset( mTarget, 0, sizeof( mTarget ) );
+    mData = NULL;
+}
+
+SP_XmlPIEvent :: ~SP_XmlPIEvent()
+{
+    if( NULL != mData ) free( mData );
+    mData = NULL;
+}
+
+void SP_XmlPIEvent :: setTarget( const char * target )
+{
+    snprintf( mTarget, sizeof( mTarget ), "%s", target );
+}
+
+const char * SP_XmlPIEvent :: getTarget()
+{
+    return mTarget;
+}
+
+void SP_XmlPIEvent :: setData( const char * data, int len )
+{
+    if( NULL != data ) {
+        if( NULL != mData ) free( mData );
+        mData = (char*)malloc( len + 1 );
+        memcpy( mData, data, len );
+        mData[ len ] = '\0';
+    }
+}
+
+const char * SP_XmlPIEvent :: getData()
+{
+    return mData;
+}
+
+//=========================================================
+
+SP_XmlDocDeclEvent :: SP_XmlDocDeclEvent()
+    : SP_XmlPullEvent( eDocDecl )
+{
+    memset( mVersion, 0, sizeof( mVersion ) );
+    memset( mEncoding, 0, sizeof( mEncoding ) );
+    mStandalone = -1;
+}
+
+SP_XmlDocDeclEvent :: ~SP_XmlDocDeclEvent()
+{
+}
+
+void SP_XmlDocDeclEvent :: setVersion( const char * version )
+{
+    strncpy( mVersion, version, sizeof( mVersion ) -1 );
+}
+
+const char * SP_XmlDocDeclEvent :: getVersion() const
+{
+    return mVersion;
+}
+
+void SP_XmlDocDeclEvent :: setEncoding( const char * encoding )
+{
+    strncpy( mEncoding, encoding, sizeof( mEncoding ) -1 );
+}
+
+const char * SP_XmlDocDeclEvent :: getEncoding() const
+{
+    return mEncoding;
+}
+
+void SP_XmlDocDeclEvent :: setStandalone( int standalone )
+{
+    mStandalone = standalone;
+}
+
+int SP_XmlDocDeclEvent :: getStandalone() const
+{
+    return mStandalone;
+}
+
+//=========================================================
+
+SP_XmlStartTagEvent :: SP_XmlStartTagEvent()
+    : SP_XmlPullEvent( eStartTag )
+{
+    mName = NULL;
+    mAttrNameList = new SP_XmlArrayList();
+    mAttrValueList = new SP_XmlArrayList();
+}
+
+SP_XmlStartTagEvent :: ~SP_XmlStartTagEvent()
+{
+    if( NULL != mName ) free( mName );
+    mName = NULL;
+
+    int i = 0;
+
+    for( i = 0; i < mAttrNameList->getCount(); i++ ) {
+        free( (char*)mAttrNameList->getItem( i ) );
+    }
+
+    delete mAttrNameList;
+    mAttrNameList = NULL;
+
+    for( i = 0; i < mAttrValueList->getCount(); i++ ) {
+        free( (char*)mAttrValueList->getItem( i ) );
+    }
+    delete mAttrValueList;
+    mAttrValueList = NULL;
+}
+
+void SP_XmlStartTagEvent :: setName( const char * name )
+{
+    if( NULL != name ) {
+        if( NULL != mName ) free( mName );
+        mName = strdup( name );
+    }
+}
+
+const char * SP_XmlStartTagEvent :: getName() const
+{
+    return mName;
+}
+
+void SP_XmlStartTagEvent :: addAttr( const char * name, const char * value )
+{
+    if( NULL != name ) mAttrNameList->append( strdup( name ) );
+    if( NULL != value ) mAttrValueList->append( strdup( value ) );
+}
+
+const char * SP_XmlStartTagEvent :: getAttrValue( const char * name ) const
+{
+    const char * ret = NULL;
+
+    for( int i = 0; i < mAttrNameList->getCount(); i++ ) {
+        if( 0 == strcmp( name, (char*)mAttrNameList->getItem( i ) ) ) {
+            ret = (char*)mAttrValueList->getItem( i );
+            break;
+        }
+    }
+
+    return ret;
+}
+
+int SP_XmlStartTagEvent :: getAttrCount() const
+{
+    return mAttrNameList->getCount();
+}
+
+const char * SP_XmlStartTagEvent :: getAttr( int index, const char ** value ) const
+{
+    const char * name = (char*)mAttrNameList->getItem( index );
+    if( NULL != name && NULL != value ) *value = (char*)mAttrValueList->getItem( index );
+
+    return name;
+}
+
+void SP_XmlStartTagEvent :: removeAttr( const char * name )
+{
+    int index = -1;
+
+    for( int i = 0; i < mAttrNameList->getCount(); i++ ) {
+        if( 0 == strcmp( name, (char*)mAttrNameList->getItem( i ) ) ) {
+            index = i;
+            break;
+        }
+    }
+
+    if( index >= 0 ) {
+        free( mAttrNameList->takeItem( index ) );
+        free( mAttrValueList->takeItem( index ) );
+    }
+}
+
+//=========================================================
+
+SP_XmlTextEvent :: SP_XmlTextEvent( int eventType )
+    : SP_XmlPullEvent( eventType )
+{
+    mText = NULL;
+}
+
+SP_XmlTextEvent :: ~SP_XmlTextEvent()
+{
+    if( NULL != mText ) free( mText );
+    mText = NULL;
+}
+
+void SP_XmlTextEvent :: setText( const char * text, int len )
+{
+    if( NULL != text ) {
+        if( NULL != mText ) free( mText );
+        mText = (char*)malloc( len + 1 );
+        memcpy( mText, text, len );
+        mText[ len ] = '\0';
+    }
+}
+
+const char * SP_XmlTextEvent :: getText() const
+{
+    return mText;
+}
+
+//=========================================================
+
+SP_XmlEndTagEvent :: SP_XmlEndTagEvent()
+    : SP_XmlTextEvent( eEndTag )
+{
+}
+
+SP_XmlEndTagEvent :: ~SP_XmlEndTagEvent()
+{
+}
+
+//=========================================================
+
+SP_XmlCDataEvent :: SP_XmlCDataEvent()
+    : SP_XmlTextEvent( eCData )
+{
+}
+
+SP_XmlCDataEvent :: ~SP_XmlCDataEvent()
+{
+}
+
+//=========================================================
+
+SP_XmlCommentEvent :: SP_XmlCommentEvent()
+    : SP_XmlTextEvent( eComment )
+{
+}
+
+SP_XmlCommentEvent :: ~SP_XmlCommentEvent()
+{
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlevent.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __spxmlevent_hpp__
+#define __spxmlevent_hpp__
+
+class SP_XmlArrayList;
+class SP_XmlQueue;
+
+class SP_XmlPullEvent {
+public:
+    enum EventType { eStartDocument, eEndDocument, ePI,
+            eDocDecl, eDocType, eStartTag, eEndTag, eCData, eComment };
+
+    SP_XmlPullEvent( int eventType );
+    virtual ~SP_XmlPullEvent();
+
+    int getEventType();
+
+private:
+    /// Private copy constructor and copy assignment ensure classes derived from
+    /// this cannot be copied.
+    SP_XmlPullEvent( SP_XmlPullEvent & );
+    SP_XmlPullEvent & operator=( SP_XmlPullEvent & );
+
+protected:
+    const int mEventType;
+};
+
+class SP_XmlPullEventQueue {
+public:
+    SP_XmlPullEventQueue();
+    ~SP_XmlPullEventQueue();
+
+    void enqueue( SP_XmlPullEvent * event );
+    SP_XmlPullEvent * dequeue();
+
+private:
+    SP_XmlPullEventQueue( SP_XmlPullEventQueue & );
+    SP_XmlPullEventQueue & operator=( SP_XmlPullEventQueue & );
+
+    SP_XmlQueue * mQueue;
+};
+
+class SP_XmlStartDocEvent : public SP_XmlPullEvent {
+public:
+    SP_XmlStartDocEvent();
+    virtual ~SP_XmlStartDocEvent();
+};
+
+class SP_XmlEndDocEvent : public SP_XmlPullEvent {
+public:
+    SP_XmlEndDocEvent();
+    virtual ~SP_XmlEndDocEvent();
+};
+
+class SP_XmlDocTypeEvent : public SP_XmlPullEvent {
+public:
+    SP_XmlDocTypeEvent();
+    virtual ~SP_XmlDocTypeEvent();
+
+    void setName( const char * name );
+    const char * getName() const;
+    void setSystemID( const char * systemID );
+    const char * getSystemID() const;
+    void setPublicID( const char * publicID );
+    const char * getPublicID() const;
+    void setDTD( const char * dtd );
+    const char * getDTD() const;
+
+private:
+    char mName[ 128 ];
+    char mSystemID[ 128 ];
+    char mPublicID[ 128 ];
+    char mDTD[ 256 ];    
+};
+
+class SP_XmlPIEvent : public SP_XmlPullEvent {
+public:
+    SP_XmlPIEvent();
+    virtual ~SP_XmlPIEvent();
+
+    void setTarget( const char * target );
+    const char * getTarget();
+
+    void setData( const char * data, int len );
+    const char * getData();
+
+private:
+    char mTarget[ 128 ];
+    char * mData;
+};
+
+class SP_XmlDocDeclEvent : public SP_XmlPullEvent {
+public:
+    SP_XmlDocDeclEvent();
+    virtual ~SP_XmlDocDeclEvent();
+
+    void setVersion( const char * version );
+    const char * getVersion() const;
+    void setEncoding( const char * encoding );
+    const char * getEncoding() const;
+    void setStandalone( int standalone );
+    int getStandalone() const;
+
+private:
+    char mVersion[ 8 ];
+    char mEncoding[ 32 ];
+    int mStandalone;
+};
+
+class SP_XmlStartTagEvent : public SP_XmlPullEvent {
+public:
+    SP_XmlStartTagEvent();
+    virtual ~SP_XmlStartTagEvent();
+
+    void setName( const char * name );
+    const char * getName() const;
+
+    void addAttr( const char * name, const char * value );
+    const char * getAttrValue( const char * name ) const;
+    int getAttrCount() const;
+
+    /// get attribute name and value by index, return attribute name
+    const char * getAttr( int index, const char ** value ) const;
+
+    void removeAttr( const char * name );
+
+private:
+    char * mName;
+    SP_XmlArrayList * mAttrNameList;
+    SP_XmlArrayList * mAttrValueList;
+};
+
+class SP_XmlTextEvent : public SP_XmlPullEvent {
+public:
+    SP_XmlTextEvent( int eventType );
+    virtual ~SP_XmlTextEvent();
+
+    void setText( const char * text, int len );
+    const char * getText() const;
+
+private:
+    char * mText;
+};
+
+class SP_XmlEndTagEvent : public SP_XmlTextEvent {
+public:
+    SP_XmlEndTagEvent();
+    virtual ~SP_XmlEndTagEvent();
+};
+
+class SP_XmlCDataEvent : public SP_XmlTextEvent {
+public:
+    SP_XmlCDataEvent();
+    virtual ~SP_XmlCDataEvent();
+};
+
+class SP_XmlCommentEvent : public SP_XmlTextEvent {
+public:
+    SP_XmlCommentEvent();
+    virtual ~SP_XmlCommentEvent();
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlhandle.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2008 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "spxmlhandle.hpp"
+#include "spxmlnode.hpp"
+
+
+SP_XmlHandle :: SP_XmlHandle( SP_XmlNode * node )
+{
+    mNode = node;
+}
+
+SP_XmlHandle :: SP_XmlHandle( const SP_XmlHandle & ref )
+{
+    mNode = ref.mNode;
+}
+
+SP_XmlHandle & SP_XmlHandle :: operator=( const SP_XmlHandle & ref )
+{
+    mNode = ref.mNode;
+    return *this;
+}
+
+SP_XmlHandle :: ~SP_XmlHandle()
+{
+}
+
+SP_XmlHandle SP_XmlHandle :: getChild( const char * name, int index ) const
+{
+    SP_XmlNode * ret = NULL;
+
+    if( NULL != mNode ) {
+        if( SP_XmlNode::eELEMENT == mNode->getType() ) {
+            SP_XmlElementNode * element = (SP_XmlElementNode*)mNode;
+            const SP_XmlNodeList * children = element->getChildren();
+
+            int tmpIndex = index;
+            for( int i = 0; i < children->getLength(); i++ ) {
+                if( SP_XmlNode::eELEMENT == children->get(i)->getType() ) {
+                    SP_XmlElementNode * iter = (SP_XmlElementNode*)children->get(i);
+                    if( 0 == strcmp( name, iter->getName() ) ) {
+                        if( 0 == tmpIndex ) {
+                            ret = iter;
+                            break;
+                        }
+                        tmpIndex--;
+                    }
+                }
+            }
+        }
+    }
+
+    return SP_XmlHandle( ret );
+}
+
+SP_XmlHandle SP_XmlHandle :: getChild( int index ) const
+{
+    SP_XmlNode * ret = NULL;
+
+    if( NULL != mNode ) {
+        if( SP_XmlNode::eELEMENT == mNode->getType() ) {
+            SP_XmlElementNode * element = (SP_XmlElementNode*)mNode;
+            ret = (SP_XmlNode*)element->getChildren()->get( index );
+        }
+    }
+
+    return SP_XmlHandle( ret );
+}
+
+SP_XmlHandle SP_XmlHandle :: getElement( int index ) const
+{
+    SP_XmlNode * ret = NULL;
+
+    if( NULL != mNode ) {
+        if( SP_XmlNode::eELEMENT == mNode->getType() ) {
+            SP_XmlElementNode * element = (SP_XmlElementNode*)mNode;
+            const SP_XmlNodeList * children = element->getChildren();
+
+            int tmpIndex = index;
+            for( int i = 0; i < children->getLength(); i++ ) {
+                if( SP_XmlNode::eELEMENT == children->get(i)->getType() ) {
+                    SP_XmlElementNode * iter = (SP_XmlElementNode*)children->get(i);
+
+                    if( 0 == tmpIndex ) {
+                        ret = iter;
+                        break;
+                    }
+                    tmpIndex--;
+                }
+            }
+        }
+    }
+
+    return SP_XmlHandle( ret );
+}
+
+SP_XmlNode * SP_XmlHandle :: toNode()
+{
+    return mNode;
+}
+
+SP_XmlElementNode * SP_XmlHandle :: toElement()
+{
+    if( NULL != mNode && SP_XmlNode::eELEMENT == mNode->getType() ) {
+        return (SP_XmlElementNode*)mNode;
+    }
+
+    return NULL;
+}
+
+SP_XmlCDataNode * SP_XmlHandle :: toCData()
+{
+    if( NULL != mNode && SP_XmlNode::eCDATA == mNode->getType() ) {
+        return (SP_XmlCDataNode*)mNode;
+    }
+
+    return NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlhandle.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2008 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __spxmlhandle_hpp__
+#define __spxmlhandle_hpp__
+
+class SP_XmlNode;
+
+class SP_XmlElementNode;
+class SP_XmlCDataNode;
+class SP_XmlCommentNode;
+
+/**
+ *  This class is a clone of TinyXML's TiXmlHandle class.
+ *
+ *
+ *  A SP_XmlHandle is a class that wraps a node pointer with null checks; this is
+ *  an incredibly useful thing. Note that SP_XmlHandle is not part of the SPXml
+ *  DOM structure. It is a separate utility class.
+ *
+
+    Take an example:
+    @verbatim
+    <Document>
+        <Element attributeA = "valueA">
+            <Child attributeB = "value1" />
+            <Child attributeB = "value2" />
+        </Element>
+    <Document>
+    @endverbatim
+
+    Assuming you want the value of "attributeB" in the 2nd "Child" element, a
+    SP_XmlHandle checks for null pointers so it is perfectly safe and correct to use:
+
+    @verbatim
+    SP_XmlHandle rootHandle( parser.getDocument()->getRootElement() );
+    SP_XmlElementNode * child2 = rootHandle.getChild( "Element" )
+            .getChild( "Child", 1 ).toElement();
+
+    if( child2 ) {
+        // do something
+    }
+    @endverbatim
+ *
+ */
+
+class SP_XmlHandle {
+public:
+    SP_XmlHandle( SP_XmlNode * node );
+    SP_XmlHandle( const SP_XmlHandle & ref );
+    SP_XmlHandle & operator=( const SP_XmlHandle & ref );
+
+    ~SP_XmlHandle();
+
+    SP_XmlHandle getChild( const char * name, int index = 0 ) const;
+
+    SP_XmlHandle getChild( int index ) const;
+
+    SP_XmlHandle getElement( int index ) const;
+
+    SP_XmlNode * toNode();
+
+    SP_XmlElementNode * toElement();
+
+    SP_XmlCDataNode * toCData();
+
+private:
+    SP_XmlNode * mNode;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlnode.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "spxmlnode.hpp"
+#include "spxmlutils.hpp"
+#include "spxmlevent.hpp"
+
+//=========================================================
+
+SP_XmlNode :: SP_XmlNode( int type )
+    : mType( type )
+{
+    mParent = NULL;
+}
+
+SP_XmlNode :: ~SP_XmlNode()
+{
+    mParent = NULL;
+}
+
+void SP_XmlNode :: setParent( SP_XmlNode * parent )
+{
+    mParent = parent;
+}
+
+const SP_XmlNode * SP_XmlNode :: getParent() const
+{
+    return mParent;
+}
+
+int SP_XmlNode :: getType() const
+{
+    return mType;
+}
+
+//=========================================================
+
+SP_XmlNodeList :: SP_XmlNodeList()
+{
+    mList = new SP_XmlArrayList();
+}
+
+SP_XmlNodeList :: ~SP_XmlNodeList()
+{
+    for( int i = 0; i < mList->getCount(); i++ ) {
+        SP_XmlNode * node = (SP_XmlNode*)mList->getItem( i );
+        delete node;
+    }
+
+    delete mList;
+
+    mList = NULL;
+}
+
+int SP_XmlNodeList :: getLength() const
+{
+    return mList->getCount();
+}
+
+void SP_XmlNodeList :: append( SP_XmlNode * node )
+{
+    mList->append( node );
+}
+
+SP_XmlNode * SP_XmlNodeList :: get( int index ) const
+{
+    return (SP_XmlNode*)mList->getItem( index );
+}
+
+SP_XmlNode * SP_XmlNodeList :: take( int index ) const
+{
+    return (SP_XmlNode*)mList->takeItem( index );
+}
+
+//=========================================================
+
+SP_XmlDocument :: SP_XmlDocument()
+    : SP_XmlNode( eXMLDOC )
+{
+    mDocDecl = NULL;
+    mDocType = NULL;
+    mChildren = new SP_XmlNodeList();
+}
+
+SP_XmlDocument :: ~SP_XmlDocument()
+{
+    if( NULL != mDocDecl ) delete mDocDecl;
+    mDocDecl = NULL;
+
+    if( NULL != mDocType ) delete mDocType;
+    mDocType = NULL;
+
+    if( NULL != mChildren ) delete mChildren;
+    mChildren = NULL;
+}
+
+void SP_XmlDocument :: setDocDecl( SP_XmlDocDeclNode * docDecl )
+{
+    if( NULL != mDocDecl ) delete mDocDecl;
+    docDecl->setParent( this );
+    mDocDecl = docDecl;
+}
+
+SP_XmlDocDeclNode * SP_XmlDocument :: getDocDecl() const
+{
+    return mDocDecl;
+}
+
+void SP_XmlDocument :: setDocType( SP_XmlDocTypeNode * docType )
+{
+    if( NULL != mDocType ) delete mDocType;
+    docType->setParent( this );
+    mDocType = docType;
+}
+
+SP_XmlDocTypeNode * SP_XmlDocument :: getDocType() const
+{
+    return mDocType;
+}
+
+void SP_XmlDocument :: setRootElement( SP_XmlElementNode * rootElement )
+{
+    int index = -1;
+    for( int i = 0; i < mChildren->getLength(); i++ ) {
+        const SP_XmlNode * node = mChildren->get( i );
+
+        if( SP_XmlNode::eELEMENT == node->getType() ) {
+            index = i;
+            break;
+        }
+    }
+
+    if( index >= 0 ) {
+        SP_XmlNode * node = mChildren->take( index );
+        delete node;
+    }
+
+    mChildren->append( rootElement );
+    rootElement->setParent( this );
+}
+
+SP_XmlElementNode * SP_XmlDocument :: getRootElement() const
+{
+    SP_XmlElementNode * ret = NULL;
+
+    for( int i = 0; i < mChildren->getLength(); i++ ) {
+        const SP_XmlNode * node = mChildren->get( i );
+
+        if( SP_XmlNode::eELEMENT == node->getType() ) {
+            ret = (SP_XmlElementNode*)node;
+            break;
+        }
+    }
+
+    return ret;
+}
+
+SP_XmlNodeList * SP_XmlDocument :: getChildren() const
+{
+    return mChildren;
+}
+
+//=========================================================
+
+SP_XmlPINode :: SP_XmlPINode()
+    : SP_XmlNode( ePI )
+{
+    mEvent = new SP_XmlPIEvent();
+}
+
+SP_XmlPINode :: SP_XmlPINode( SP_XmlPIEvent * event )
+    : SP_XmlNode( ePI )
+{
+    mEvent = event;
+}
+
+SP_XmlPINode :: ~SP_XmlPINode()
+{
+    if( NULL != mEvent ) delete mEvent;
+    mEvent = NULL;
+}
+
+void SP_XmlPINode :: setTarget( const char * target )
+{
+    mEvent->setTarget( target );
+}
+
+const char * SP_XmlPINode :: getTarget()
+{
+    return mEvent->getTarget();
+}
+
+void SP_XmlPINode :: setData( const char * data )
+{
+    mEvent->setData( data, strlen( data ) );
+}
+
+const char * SP_XmlPINode :: getData()
+{
+    return mEvent->getData();
+}
+
+//=========================================================
+
+SP_XmlDocDeclNode :: SP_XmlDocDeclNode()
+    : SP_XmlNode( eDOCDECL )
+{
+    mEvent = new SP_XmlDocDeclEvent();
+}
+
+SP_XmlDocDeclNode :: SP_XmlDocDeclNode( SP_XmlDocDeclEvent * event )
+    : SP_XmlNode( eDOCDECL )
+{
+    mEvent = event;
+}
+
+SP_XmlDocDeclNode :: ~SP_XmlDocDeclNode()
+{
+    if( NULL != mEvent ) delete mEvent;
+    mEvent = NULL;
+}
+
+void SP_XmlDocDeclNode :: setVersion( const char * version )
+{
+    mEvent->setVersion( version );
+}
+
+const char * SP_XmlDocDeclNode :: getVersion() const
+{
+    return mEvent->getVersion();
+}
+
+void SP_XmlDocDeclNode :: setEncoding( const char * encoding )
+{
+    mEvent->setEncoding( encoding );
+}
+
+const char * SP_XmlDocDeclNode :: getEncoding() const
+{
+    return mEvent->getEncoding();
+}
+
+void SP_XmlDocDeclNode :: setStandalone( int standalone )
+{
+    mEvent->setStandalone( standalone );
+}
+
+int SP_XmlDocDeclNode :: getStandalone() const
+{
+    return mEvent->getStandalone();
+}
+
+//=========================================================
+
+SP_XmlDocTypeNode :: SP_XmlDocTypeNode()
+    : SP_XmlNode( eDOCTYPE )
+{
+    mEvent = new SP_XmlDocTypeEvent();
+}
+
+SP_XmlDocTypeNode :: SP_XmlDocTypeNode( SP_XmlDocTypeEvent * event )
+    : SP_XmlNode( eDOCTYPE )
+{
+    mEvent = event;
+}
+
+SP_XmlDocTypeNode :: ~SP_XmlDocTypeNode()
+{
+    if( NULL != mEvent ) delete mEvent;
+    mEvent = NULL;
+}
+
+void SP_XmlDocTypeNode :: setName( const char * name )
+{
+    mEvent->setName( name );
+}
+
+const char * SP_XmlDocTypeNode :: getName() const
+{
+    return mEvent->getName();
+}
+
+void SP_XmlDocTypeNode :: setSystemID( const char * systemID )
+{
+    mEvent->setSystemID( systemID );
+}
+
+const char * SP_XmlDocTypeNode :: getSystemID() const
+{
+    return mEvent->getSystemID();
+}
+
+void SP_XmlDocTypeNode :: setPublicID( const char * publicID )
+{
+    mEvent->setPublicID( publicID );
+}
+
+const char * SP_XmlDocTypeNode :: getPublicID() const
+{
+    return mEvent->getPublicID();
+}
+
+void SP_XmlDocTypeNode :: setDTD( const char * dtd )
+{
+    mEvent->setDTD( dtd );
+}
+
+const char * SP_XmlDocTypeNode :: getDTD() const
+{
+    return mEvent->getDTD();
+}
+
+//=========================================================
+
+SP_XmlElementNode :: SP_XmlElementNode()
+    : SP_XmlNode( eELEMENT )
+{
+    mEvent = new SP_XmlStartTagEvent();
+    mChildren = new SP_XmlNodeList();
+}
+
+SP_XmlElementNode :: SP_XmlElementNode( SP_XmlStartTagEvent * event )
+    : SP_XmlNode( eELEMENT )
+{
+    mEvent = event;
+    mChildren = new SP_XmlNodeList();
+}
+
+SP_XmlElementNode :: ~SP_XmlElementNode()
+{
+    if( NULL != mEvent ) delete mEvent;
+    mEvent = NULL;
+
+    if( NULL != mChildren ) delete mChildren;
+    mChildren = NULL;
+}
+
+void SP_XmlElementNode :: setName( const char * name )
+{
+    mEvent->setName( name );
+}
+
+const char * SP_XmlElementNode :: getName() const
+{
+    return mEvent->getName();
+}
+
+void SP_XmlElementNode :: addChild( SP_XmlNode * node )
+{
+    node->setParent( this );
+    mChildren->append( node );
+}
+
+const SP_XmlNodeList * SP_XmlElementNode :: getChildren() const
+{
+    return mChildren;
+}
+
+void SP_XmlElementNode :: addAttr( const char * name, const char * value )
+{
+    mEvent->addAttr( name, value );
+}
+
+const char * SP_XmlElementNode :: getAttrValue( const char * name ) const
+{
+    return mEvent->getAttrValue( name );
+}
+
+int SP_XmlElementNode :: getAttrCount() const
+{
+    return mEvent->getAttrCount();
+}
+
+const char * SP_XmlElementNode :: getAttr( int index, const char ** value ) const
+{
+    return mEvent->getAttr( index, value );
+}
+
+void SP_XmlElementNode :: removeAttr( const char * name )
+{
+    mEvent->removeAttr( name );
+}
+
+//=========================================================
+
+SP_XmlCDataNode :: SP_XmlCDataNode()
+    : SP_XmlNode( eCDATA )
+{
+    mEvent = new SP_XmlCDataEvent();
+}
+
+SP_XmlCDataNode :: SP_XmlCDataNode( SP_XmlCDataEvent * event )
+    : SP_XmlNode( eCDATA )
+{
+    mEvent = event;
+}
+
+SP_XmlCDataNode :: ~SP_XmlCDataNode()
+{
+    if( NULL != mEvent ) delete mEvent;
+    mEvent = NULL;
+}
+
+void SP_XmlCDataNode :: setText( const char * content )
+{
+    mEvent->setText( content, strlen( content ) );
+}
+
+const char * SP_XmlCDataNode :: getText() const
+{
+    return mEvent->getText();
+}
+
+//=========================================================
+
+SP_XmlCommentNode :: SP_XmlCommentNode()
+    : SP_XmlNode( eCOMMENT )
+{
+    mEvent = new SP_XmlCommentEvent();
+}
+
+SP_XmlCommentNode :: SP_XmlCommentNode( SP_XmlCommentEvent * event )
+    : SP_XmlNode( eCOMMENT )
+{
+    mEvent = event;
+}
+
+SP_XmlCommentNode :: ~SP_XmlCommentNode()
+{
+    if( NULL != mEvent ) delete mEvent;
+    mEvent = NULL;
+}
+
+void SP_XmlCommentNode :: setText( const char * comment )
+{
+    mEvent->setText( comment, strlen( comment ) );
+}
+
+const char * SP_XmlCommentNode :: getText() const
+{
+    return mEvent->getText();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlnode.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __spxmlnode_hpp__
+#define __spxmlnode_hpp__
+
+class SP_XmlArrayList;
+
+class SP_XmlNode {
+public:
+    enum { eXMLDOC, eDOCDECL, ePI, eDOCTYPE, eELEMENT, eCDATA, eCOMMENT  };
+
+    SP_XmlNode( int type );
+    virtual ~SP_XmlNode();
+
+    void setParent( SP_XmlNode * parent );
+    const SP_XmlNode * getParent() const;
+    int getType() const;
+
+protected:
+    SP_XmlNode( SP_XmlNode & );
+    SP_XmlNode & operator=( SP_XmlNode & );
+
+private:
+    SP_XmlNode * mParent;
+    const int mType;
+};
+
+class SP_XmlNodeList {
+public:
+    SP_XmlNodeList();
+    ~SP_XmlNodeList();
+
+    int getLength() const;
+    void append( SP_XmlNode * node );
+    SP_XmlNode * get( int index ) const;
+    SP_XmlNode * take( int index ) const;
+
+private:
+    SP_XmlNodeList( SP_XmlNodeList & );
+    SP_XmlNodeList & operator=( SP_XmlNodeList & );
+
+    SP_XmlArrayList * mList;
+};
+
+class SP_XmlPIEvent;
+class SP_XmlDocDeclEvent;
+class SP_XmlDocTypeEvent;
+class SP_XmlStartTagEvent;
+class SP_XmlCDataEvent;
+class SP_XmlCommentEvent;
+
+class SP_XmlElementNode;
+class SP_XmlDocDeclNode;
+class SP_XmlDocTypeNode;
+
+class SP_XmlDocument : public SP_XmlNode {
+public:
+    SP_XmlDocument();
+    virtual ~SP_XmlDocument();
+
+    void setDocDecl( SP_XmlDocDeclNode * docDecl );
+    SP_XmlDocDeclNode * getDocDecl() const;
+    void setDocType( SP_XmlDocTypeNode * docType );
+    SP_XmlDocTypeNode * getDocType() const;
+    void setRootElement( SP_XmlElementNode * rootElement );
+    SP_XmlElementNode * getRootElement() const;
+    SP_XmlNodeList * getChildren() const;
+
+private:
+    SP_XmlDocDeclNode * mDocDecl;
+    SP_XmlDocTypeNode * mDocType;
+    SP_XmlNodeList * mChildren;
+};
+
+class SP_XmlPINode : public SP_XmlNode {
+public:
+    SP_XmlPINode();
+    SP_XmlPINode( SP_XmlPIEvent * event );
+    virtual ~SP_XmlPINode();
+
+    void setTarget( const char * target );
+    const char * getTarget();
+
+    void setData( const char * data );
+    const char * getData();
+
+private:
+    SP_XmlPIEvent * mEvent;
+};
+
+class SP_XmlDocDeclNode : public SP_XmlNode {
+public:
+    SP_XmlDocDeclNode();
+    SP_XmlDocDeclNode( SP_XmlDocDeclEvent * event );
+    virtual ~SP_XmlDocDeclNode();
+
+    void setVersion( const char * version );
+    const char * getVersion() const;
+    void setEncoding( const char * encoding );
+    const char * getEncoding() const;
+    void setStandalone( int standalone );
+    int getStandalone() const;
+
+private:
+    SP_XmlDocDeclEvent * mEvent;
+};
+
+class SP_XmlDocTypeNode : public SP_XmlNode {
+public:
+    SP_XmlDocTypeNode();
+    SP_XmlDocTypeNode( SP_XmlDocTypeEvent * event );
+    virtual ~SP_XmlDocTypeNode();
+
+    void setName( const char * name );
+    const char * getName() const;
+    void setSystemID( const char * systemID );
+    const char * getSystemID() const;
+    void setPublicID( const char * publicID );
+    const char * getPublicID() const;
+    void setDTD( const char * dtd );
+    const char * getDTD() const;
+
+private:
+    SP_XmlDocTypeEvent * mEvent;
+};
+
+class SP_XmlElementNode : public SP_XmlNode {
+public:
+    SP_XmlElementNode();
+    SP_XmlElementNode( SP_XmlStartTagEvent * event );
+    virtual ~SP_XmlElementNode();
+
+    void setName( const char * name );
+    const char * getName() const;
+    void addChild( SP_XmlNode * node );
+    const SP_XmlNodeList * getChildren() const;
+
+    void addAttr( const char * name, const char * value );
+    const char * getAttrValue( const char * name ) const;
+    int getAttrCount() const;
+    const char * getAttr( int index, const char ** value ) const;
+
+    void removeAttr( const char * name );
+
+protected:
+    SP_XmlStartTagEvent * mEvent;
+    SP_XmlNodeList * mChildren;
+};
+
+class SP_XmlCDataNode : public SP_XmlNode {
+public:
+    SP_XmlCDataNode();
+    SP_XmlCDataNode( SP_XmlCDataEvent * event );
+    virtual ~SP_XmlCDataNode();
+
+    void setText( const char * content );
+    const char * getText() const;
+
+protected:
+    SP_XmlCDataEvent * mEvent;
+};
+
+class SP_XmlCommentNode : public SP_XmlNode {
+public:
+    SP_XmlCommentNode();
+    SP_XmlCommentNode( SP_XmlCommentEvent * event );
+    virtual ~SP_XmlCommentNode();
+
+    void setText( const char * comment );
+    const char * getText() const;
+
+protected:
+    SP_XmlCommentEvent * mEvent;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlparser.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <typeinfo>
+
+#include "mstring.h"
+
+#include "spxmlparser.hpp"
+#include "spxmlreader.hpp"
+#include "spxmlutils.hpp"
+#include "spxmlevent.hpp"
+#include "spxmlcodec.hpp"
+
+SP_XmlPullParser :: SP_XmlPullParser()
+{
+    mReaderPool = new SP_XmlReaderPool();
+    mReader = getReader( SP_XmlReader::eLBracket );
+    mEventQueue = new SP_XmlPullEventQueue();
+    mEventQueue->enqueue( new SP_XmlStartDocEvent() );
+
+    mRootTagState = eRootNone;
+    mTagNameStack = new SP_XmlArrayList();
+    mLevel = 0;
+
+    mIgnoreWhitespace = 1;
+
+    mError = NULL;
+
+    memset( mErrorSegment, 0, sizeof( mErrorSegment ) );
+    mErrorIndex = 0;
+    mRowIndex = mColIndex = 0;
+
+    memset( mEncoding, 0, sizeof( mEncoding ) );
+}
+
+SP_XmlPullParser :: ~SP_XmlPullParser()
+{
+    mReaderPool->save( mReader );
+
+    for( int i = 0; i < mTagNameStack->getCount(); i++ ) {
+        free( (char*)mTagNameStack->getItem( i ) );
+    }
+    delete mTagNameStack;
+
+    delete mEventQueue;
+
+    delete mReaderPool;
+
+    if( NULL != mError ) free( mError );    
+}
+
+const char * SP_XmlPullParser :: getEncoding()
+{
+    if( '\0' == mEncoding[0] ) {
+        return SP_XmlStringCodec::DEFAULT_ENCODING;
+    }
+
+    return mEncoding;
+}
+
+int SP_XmlPullParser :: append( const char * source, int len )
+{
+    if( NULL != mError ) return 0;
+
+    int consumed = 0;
+
+    for( int i = 0; i < len && NULL == mError; i++ ) {
+
+        consumed++;
+
+        char c = source[ i ];
+
+        mErrorSegment[ mErrorIndex++ % sizeof( mErrorSegment ) ] = c;
+        mReader->read( this, c );
+        if( '\n' == c ) {
+            mRowIndex++;
+            mColIndex = 0;
+        } else {
+            mColIndex++;
+        }
+    }
+
+    return consumed;
+}
+
+SP_XmlPullEvent * SP_XmlPullParser :: getNext()
+{
+    SP_XmlPullEvent * event = mEventQueue->dequeue();
+
+    if( NULL != event ) {
+        if( SP_XmlPullEvent::eStartTag == event->getEventType() ) mLevel++;
+        if( SP_XmlPullEvent::eEndTag == event->getEventType() ) mLevel--;
+    }
+
+    return event;
+}
+
+int SP_XmlPullParser :: getLevel()
+{
+    return mLevel;
+}
+
+void SP_XmlPullParser :: setIgnoreWhitespace( int ignoreWhitespace )
+{
+    mIgnoreWhitespace = ignoreWhitespace;
+}
+
+int SP_XmlPullParser :: getIgnoreWhitespace()
+{
+    return mIgnoreWhitespace;
+}
+
+const char * SP_XmlPullParser :: getError()
+{
+    return mError;
+}
+
+void SP_XmlPullParser :: changeReader( SP_XmlReader * reader )
+{
+    SP_XmlPullEvent * event = mReader->getEvent( this );
+    if( NULL != event ) {
+        if( SP_XmlPullEvent::eStartTag == event->getEventType() ) {
+            if( eRootNone == mRootTagState ) mRootTagState = eRootStart;
+            const char * name = ((SP_XmlStartTagEvent*)event)->getName();
+            mTagNameStack->append( strdup( name ) );
+        }
+        if( SP_XmlPullEvent::eEndTag == event->getEventType() ) {
+            char error[ 256 ] = { 0 };
+
+            const char * etag = ((SP_XmlEndTagEvent*)event)->getText();
+            char * stag = (char*)mTagNameStack->takeItem( SP_XmlArrayList::LAST_INDEX );
+            if( NULL != stag ) {
+                if( 0 != strcmp( stag, etag ) ) {
+                    snprintf( error, sizeof( error ),
+                            "mismatched tag, start-tag <%s>, end-tag <%s>", stag, etag );
+                }
+                free( stag );
+            } else {
+                snprintf( error, sizeof( error ),
+                        "mismatched tag, start-tag <NULL>, end-tag <%s>", etag );
+            }
+
+            if( '\0' != *error ) {
+                setError( error );
+                delete event;
+                event = NULL;
+            }
+        }
+
+        if( NULL != event ) {
+            if( SP_XmlPullEvent::eDocDecl == event->getEventType() ) {
+                snprintf( mEncoding, sizeof( mEncoding ), "%s",
+                    ((SP_XmlDocDeclEvent*)event)->getEncoding() );
+            }
+            mEventQueue->enqueue( event );
+            if( mTagNameStack->getCount() <= 0 && eRootStart == mRootTagState ) {
+                mRootTagState = eRootEnd;
+                mEventQueue->enqueue( new SP_XmlEndDocEvent() );
+            }
+        }
+    }
+
+    //printf( "\nchange: %s -> %s\n", typeid( *mReader ).name(), typeid( *reader ).name() );
+
+    mReaderPool->save( mReader );
+    mReader = reader;
+}
+
+SP_XmlReader * SP_XmlPullParser :: getReader( int type )
+{
+    return mReaderPool->borrow( type );
+}
+
+void SP_XmlPullParser :: setError( const char * error )
+{
+    if( NULL != error ) {
+        if( NULL != mError ) free( mError );
+
+        char segment[ 2 * sizeof( mErrorSegment ) + 1 ];
+        {
+            memset( segment, 0, sizeof( segment ) );
+
+            char temp[ sizeof( mErrorSegment ) + 1 ];
+            memset( temp, 0, sizeof( temp ) );
+            if( mErrorIndex < (int)sizeof( mErrorSegment ) ) {
+                strncpy( temp, mErrorSegment, mErrorIndex );
+            } else {
+                int offset = mErrorIndex % sizeof( mErrorSegment );
+                strncpy( temp, mErrorSegment + offset, sizeof( mErrorSegment ) - offset );
+                strncpy( temp + sizeof( mErrorSegment ) - offset, mErrorSegment, offset );
+            }
+
+            for( char * pos = temp, * dest = segment; '\0' != *pos; pos++ ) {
+                if( '\r' == *pos ) {
+                    *dest++ = '\\';
+                    *dest++ = 'r';
+                } else if( '\n' == *pos ) {
+                    *dest++ = '\\';
+                    *dest++ = 'n';
+                } else if( '\t' == *pos ) {
+                    *dest++ = '\\';
+                    *dest++ = 't';
+                } else {
+                    *dest++ = *pos;
+                }
+            }
+        }
+
+        char msg[ 512 ];
+        snprintf( msg, sizeof( msg), "%s ( occured at row(%d), col(%d) : %s )",
+                error, mRowIndex + 1, mColIndex + 1, segment );
+
+        mError = strdup( msg );
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlparser.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __xmlparser_hpp__
+#define __xmlparser_hpp__
+
+class SP_XmlPullEvent;
+class SP_XmlPullEventQueue;
+class SP_XmlReader;
+class SP_XmlReaderPool;
+class SP_XmlArrayList;
+
+class SP_XmlPullParser {
+public:
+    SP_XmlPullParser();
+    ~SP_XmlPullParser();
+
+    /// append more input xml source
+    /// @return how much byte has been consumed
+    int append( const char * source, int len );
+
+    /// @return NOT NULL : the pull event
+    /// @return NULL : error or need more input
+    SP_XmlPullEvent * getNext();    
+
+    /// @return NOT NULL : the detail error message
+    /// @return NULL : no error
+    const char * getError();
+
+    int getLevel();
+
+    /// default ignoreWhitespace is true
+    void setIgnoreWhitespace( int ignoreWhitespace );
+
+    int getIgnoreWhitespace();
+
+    const char * getEncoding();
+
+protected:
+    void changeReader( SP_XmlReader * reader );
+
+    SP_XmlReader * getReader( int type );
+
+    void setError( const char * error );
+
+    friend class SP_XmlReader;
+
+private:
+    SP_XmlPullEventQueue * mEventQueue;
+    SP_XmlReader * mReader;
+    SP_XmlReaderPool * mReaderPool;
+    SP_XmlArrayList * mTagNameStack;
+
+    enum { eRootNone, eRootStart, eRootEnd };
+    int mRootTagState;
+
+    int mLevel;
+
+    int mIgnoreWhitespace;
+
+    char * mError;
+
+    char mErrorSegment[ 32 ];
+    int mErrorIndex;
+    int mColIndex, mRowIndex;
+
+    char mEncoding[ 32 ];
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlreader.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,572 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <typeinfo>
+
+#include "spxmlparser.hpp"
+#include "spxmlreader.hpp"
+#include "spxmlutils.hpp"
+#include "spxmlstag.hpp"
+#include "spxmlevent.hpp"
+#include "spxmlcodec.hpp"
+
+//=========================================================
+
+SP_XmlReader :: SP_XmlReader()
+{
+    mBuffer = new SP_XmlStringBuffer();
+}
+
+SP_XmlReader :: ~SP_XmlReader()
+{
+    delete mBuffer;
+}
+
+void SP_XmlReader :: changeReader(
+        SP_XmlPullParser * parser, SP_XmlReader * reader )
+{
+    parser->changeReader( reader );
+}
+
+SP_XmlReader * SP_XmlReader :: getReader( SP_XmlPullParser * parser, int type )
+{
+    return parser->getReader( type );
+}
+
+void SP_XmlReader :: setError( SP_XmlPullParser * parser, const char * error )
+{
+    parser->setError( error );
+}
+
+void SP_XmlReader :: reset()
+{
+    mBuffer->clean();
+}
+
+//=========================================================
+
+SP_XmlPIReader :: SP_XmlPIReader()
+{
+}
+
+SP_XmlPIReader :: ~SP_XmlPIReader()
+{
+}
+
+void SP_XmlPIReader :: read( SP_XmlPullParser * parser, char c )
+{
+    if( '>' == c ) {
+        changeReader( parser, getReader( parser, SP_XmlReader::ePCData ) );
+    } else {
+        mBuffer->append( c );
+    }
+}
+
+SP_XmlPullEvent * SP_XmlPIReader :: getEvent( SP_XmlPullParser * parser )
+{
+    SP_XmlPullEvent * retEvent = NULL;
+
+    if( mBuffer->getSize() > 0 ) {
+        char * begin = (char*)mBuffer->getBuffer();
+        for( ; isspace( *begin ); ) begin++;
+
+        char * end = begin;
+        for( ; '\0' != *end && '?' != *end && ( ! isspace( *end ) ); ) end++;
+
+        char savedChar = *end;
+        *end = '\0';
+
+        if( 0 == strcasecmp( begin, "xml" ) ) {
+            *end = savedChar;
+
+            retEvent = parseDocDeclEvent( parser, mBuffer );
+        } else {
+            SP_XmlPIEvent * piEvent = new SP_XmlPIEvent();
+            piEvent->setTarget( begin );
+
+            *end = savedChar;
+
+            begin = end;
+            for( ; isspace( *begin ); ) begin++;
+
+            end = begin;
+            for( ; '\0' != *end && '?' != *end; ) end++;
+
+            piEvent->setData( begin, end - begin );
+
+            retEvent = piEvent;
+        }
+    }
+
+    return retEvent;
+}
+
+SP_XmlPullEvent * SP_XmlPIReader :: parseDocDeclEvent( SP_XmlPullParser * parser,
+        SP_XmlStringBuffer * buffer )
+{
+    SP_XmlDocDeclEvent * retEvent = NULL;
+
+    SP_XmlSTagParser tagParser( parser->getEncoding() );
+
+    tagParser.append( buffer->getBuffer(), buffer->getSize() );
+    tagParser.append( " ", 1 );
+
+    if( NULL == tagParser.getError() ) {
+        SP_XmlStartTagEvent * event = tagParser.takeEvent();
+
+        const char * version = event->getAttrValue( "version" );
+        const char * encoding = event->getAttrValue( "encoding" );
+        const char * standalone = event->getAttrValue( "standalone" );
+
+        retEvent = new SP_XmlDocDeclEvent();
+        retEvent->setVersion( NULL == version ? "" : version );
+        retEvent->setEncoding( NULL == encoding ? "" : encoding );
+        if( NULL != standalone ) {
+            if( 0 == strcasecmp( "no", standalone ) ) {
+                retEvent->setStandalone( 0 );
+            } else {
+                retEvent->setStandalone( 1 );
+            }
+        }
+
+        delete event;
+    } else {
+        setError( parser, tagParser.getError() );
+    }
+
+    return retEvent;
+}
+
+//=========================================================
+
+SP_XmlStartTagReader :: SP_XmlStartTagReader()
+{
+    mIsQuot = 0;
+}
+
+SP_XmlStartTagReader :: ~SP_XmlStartTagReader()
+{
+}
+
+void SP_XmlStartTagReader :: read( SP_XmlPullParser * parser, char c )
+{
+    if( '>' == c && 0 == mIsQuot ) {
+        changeReader( parser, getReader( parser, SP_XmlReader::ePCData ) );
+    } else if( '/' == c && 0 == mIsQuot ) {
+        SP_XmlReader * reader = getReader( parser, SP_XmlReader::eETag );
+        const char * pos = mBuffer->getBuffer();
+        for( ; isspace( *pos ); ) pos++;
+        for( ; 0 == isspace( *pos ) && '\0' != *pos; pos++ ) {
+            reader->read( parser, *pos );
+        }
+        changeReader( parser, reader );
+    } else if( '<' == c && 0 == mIsQuot ) {
+        setError( parser, "illegal char" );
+    } else {
+        mBuffer->append( c );
+
+        if( 0 == mIsQuot ) {
+            if( '\'' == c ) mIsQuot = 1;
+            if( '"' == c ) mIsQuot = 2;
+        } else {
+            if( 1 == mIsQuot && '\'' == c ) mIsQuot = 0;
+            if( 2 == mIsQuot && '"' == c ) mIsQuot = 0;
+        }
+    }
+}
+
+SP_XmlPullEvent * SP_XmlStartTagReader :: getEvent( SP_XmlPullParser * parser )
+{
+    SP_XmlStartTagEvent * retEvent = NULL;
+
+    SP_XmlSTagParser tagParser( parser->getEncoding() );
+    tagParser.append( mBuffer->getBuffer(), mBuffer->getSize() );
+    tagParser.append( " ", 1 );
+
+    if( NULL == tagParser.getError() ) {
+        retEvent = tagParser.takeEvent();
+    } else {
+        setError( parser, tagParser.getError() );
+    }
+
+    return retEvent;
+}
+
+void SP_XmlStartTagReader :: reset()
+{
+    SP_XmlReader::reset();
+    mIsQuot = 0;
+}
+
+//=========================================================
+
+SP_XmlEndTagReader :: SP_XmlEndTagReader()
+{
+}
+
+SP_XmlEndTagReader :: ~SP_XmlEndTagReader()
+{
+}
+
+void SP_XmlEndTagReader :: read( SP_XmlPullParser * parser,    char c )
+{
+    if( '>' == c ) {
+        changeReader( parser, getReader( parser, SP_XmlReader::ePCData ) );
+    } else if( '/' == c ) {
+        setError( parser, "illegal name char" );
+    } else {
+        mBuffer->append( c );
+    }
+}
+
+SP_XmlPullEvent * SP_XmlEndTagReader :: getEvent( SP_XmlPullParser * parser )
+{
+    const char * end = mBuffer->getBuffer() + mBuffer->getSize() - 1;
+
+    for( ; end > mBuffer->getBuffer() && isspace( *end ); ) end--;
+
+    SP_XmlEndTagEvent * retEvent = new SP_XmlEndTagEvent();
+    retEvent->setText( mBuffer->getBuffer(), end - mBuffer->getBuffer() + 1 );
+
+    return retEvent;
+}
+
+//=========================================================
+
+SP_XmlPCDataReader :: SP_XmlPCDataReader()
+{
+}
+
+SP_XmlPCDataReader :: ~SP_XmlPCDataReader()
+{
+}
+
+void SP_XmlPCDataReader :: read( SP_XmlPullParser * parser, char c )
+{
+    if( '<' == c ) {
+        SP_XmlReader * reader = getReader( parser, SP_XmlReader::eLBracket );
+        reader->read( parser, c );
+        changeReader( parser, reader );
+    } else {
+        mBuffer->append( c );
+    }
+}
+
+SP_XmlPullEvent * SP_XmlPCDataReader :: getEvent( SP_XmlPullParser * parser )
+{
+    SP_XmlCDataEvent * retEvent = NULL;
+
+    int ignore = 0;
+
+    if( 0 != parser->getIgnoreWhitespace() ) {
+        ignore = 1;
+        for( const char * pos = mBuffer->getBuffer(); '\0' != *pos; pos++ ) {
+            if( !isspace( *pos ) ) {
+                ignore = 0;
+                break;
+            }
+        }
+    }
+
+    if( 0 == ignore && mBuffer->getSize() > 0 ) {
+        retEvent = new SP_XmlCDataEvent();
+        SP_XmlStringBuffer buffer;
+        SP_XmlStringCodec::decode( parser->getEncoding(), mBuffer->getBuffer(), &buffer );
+        retEvent->setText( buffer.getBuffer(), buffer.getSize() );
+    }
+
+    return retEvent;
+}
+
+//=========================================================
+
+SP_XmlCDataSectionReader :: SP_XmlCDataSectionReader()
+{
+}
+
+SP_XmlCDataSectionReader :: ~SP_XmlCDataSectionReader()
+{
+}
+
+void SP_XmlCDataSectionReader :: read( SP_XmlPullParser * parser, char c )
+{
+    if( '>' == c && mBuffer->getSize() > 2 ) {
+        char last1 = mBuffer->getBuffer()[ mBuffer->getSize() - 1 ];
+        char last2 = mBuffer->getBuffer()[ mBuffer->getSize() - 2 ];
+
+        if( ']' == last1 && ']' == last2 ) {
+            changeReader( parser, getReader( parser, SP_XmlReader::ePCData ) );
+        } else {
+            mBuffer->append( c );
+        }
+    } else {
+        mBuffer->append( c );
+    }
+}
+
+SP_XmlPullEvent * SP_XmlCDataSectionReader :: getEvent( SP_XmlPullParser * parser )
+{
+    SP_XmlCDataEvent * retEvent = NULL;
+
+    int len = mBuffer->getSize();
+    const char * data = mBuffer->getBuffer();
+    if( 0 == strncmp( data, "CDATA[", strlen( "CDATA[" ) ) ) {
+        data += strlen( "CDATA[" );
+        len -= strlen( "CDATA[" );
+    }
+
+    int ignore = 0;
+    if( 0 != parser->getIgnoreWhitespace() ) {
+        ignore = 1;
+        for( int i = 0; i < len - 2; i++ ) {
+            if( !isspace( data[i] ) ) {
+                ignore = 0;
+                break;
+            }
+        }
+    }
+
+    if( 0 == ignore && len > 2 ) {
+        retEvent = new SP_XmlCDataEvent();
+        retEvent->setText( data, len - 2 );
+    }
+
+    return retEvent;
+}
+
+//=========================================================
+
+SP_XmlCommentReader :: SP_XmlCommentReader()
+{
+}
+
+SP_XmlCommentReader :: ~SP_XmlCommentReader()
+{
+}
+
+void SP_XmlCommentReader :: read( SP_XmlPullParser * parser, char c )
+{
+    if( '>' == c && mBuffer->getSize() >= 2 ) {
+        int size = mBuffer->getSize();
+        if( '-' == mBuffer->getBuffer()[ size - 1 ]
+                && '-' == mBuffer->getBuffer()[ size - 2 ] ) {
+            changeReader( parser, getReader( parser, SP_XmlReader::ePCData ) );
+        } else {
+            mBuffer->append( c );
+        }
+    } else {
+        mBuffer->append( c );
+    }
+}
+
+SP_XmlPullEvent * SP_XmlCommentReader :: getEvent( SP_XmlPullParser * parser )
+{
+    SP_XmlCommentEvent * retEvent = new SP_XmlCommentEvent();
+
+    retEvent->setText( mBuffer->getBuffer(), mBuffer->getSize() - 2 );
+
+    return retEvent;
+}
+
+//=========================================================
+
+SP_XmlDocTypeReader :: SP_XmlDocTypeReader()
+{
+}
+
+SP_XmlDocTypeReader :: ~SP_XmlDocTypeReader()
+{
+}
+
+void SP_XmlDocTypeReader :: read( SP_XmlPullParser * parser, char c )
+{
+    if( '>' == c ) {
+        if( NULL != strchr( mBuffer->getBuffer(), '[' ) ) {
+            char last = mBuffer->getBuffer()[ mBuffer->getSize() - 1 ];
+            if( ']' == last ) {
+                changeReader( parser, getReader( parser, SP_XmlReader::ePCData ) );
+            } else {
+                mBuffer->append( c );
+            }
+        } else {
+            changeReader( parser, getReader( parser, SP_XmlReader::ePCData ) );
+        }
+    } else {
+        mBuffer->append( c );
+    }
+}
+
+SP_XmlPullEvent * SP_XmlDocTypeReader :: getEvent( SP_XmlPullParser * parser )
+{
+    SP_XmlDocTypeEvent * retEvent = NULL;
+
+    SP_XmlSTagParser tagParser( parser->getEncoding() );
+
+    tagParser.append( "DOCTYPE ", strlen( "DOCTYPE " ) );
+    tagParser.append( mBuffer->getBuffer(), mBuffer->getSize() );
+    tagParser.append( " ", 1 );
+    if( NULL == tagParser.getError() ) {
+        SP_XmlStartTagEvent * event = tagParser.takeEvent();
+
+        retEvent = new SP_XmlDocTypeEvent();
+
+        for( int i = 0; i < event->getAttrCount(); i += 2 ) {
+            const char * name = event->getAttr( i, NULL );
+            if( 0 == strcmp( name, "DOCTYPE" ) ) {
+                name = event->getAttr( i + 1, NULL );
+                retEvent->setName( NULL == name ? "" : name );    
+            } else if( 0 == strcmp( name, "PUBLIC" ) ) {
+                name = event->getAttr( i + 1, NULL );
+                retEvent->setPublicID( NULL == name ? "" : name );
+            } else if( 0 == strcmp( name, "SYSTEM" ) ) {
+                name = event->getAttr( i + 1, NULL );
+                retEvent->setSystemID( NULL == name ? "" : name );
+            } else if( NULL != strstr( name, ".dtd" ) ) {
+                retEvent->setDTD( name );
+            }
+        }
+
+        delete event;
+    } else {
+        //setError( parser, tagParser.getError() );
+    }
+
+    return retEvent;
+}
+
+//=========================================================
+
+SP_XmlLeftBracketReader :: SP_XmlLeftBracketReader()
+{
+    mHasReadBracket = 0;
+}
+
+SP_XmlLeftBracketReader :: ~SP_XmlLeftBracketReader()
+{
+}
+
+void SP_XmlLeftBracketReader :: read( SP_XmlPullParser * parser, char c )
+{
+    if( 0 == mHasReadBracket ) {
+        if( isspace( c ) ) {
+            //skip
+        } else if( '<' == c ) {
+            mHasReadBracket = 1;
+        }
+    } else {
+        if( '?' == c ) {
+            changeReader( parser, getReader( parser, SP_XmlReader::ePI ) );
+        } else if( '/' == c ) {
+            changeReader( parser, getReader( parser, SP_XmlReader::eETag ) );
+        } else if( '!' == c ) {
+            changeReader( parser, getReader( parser, SP_XmlReader::eSign ) );
+        } else if( SP_XmlStringCodec::isNameChar( parser->getEncoding(), c ) ) {
+            SP_XmlReader * reader = getReader( parser, SP_XmlReader::eSTag );
+            reader->read( parser, c );
+            changeReader( parser, reader );
+        } else {
+            setError( parser, "not well-formed" );
+        }
+    }
+}
+
+SP_XmlPullEvent * SP_XmlLeftBracketReader :: getEvent( SP_XmlPullParser * parser )
+{
+    return NULL;
+}
+
+void SP_XmlLeftBracketReader :: reset()
+{
+    SP_XmlReader::reset();
+    mHasReadBracket = 0;
+}
+
+//=========================================================
+
+SP_XmlSignReader :: SP_XmlSignReader()
+{
+}
+
+SP_XmlSignReader :: ~SP_XmlSignReader()
+{
+}
+
+void SP_XmlSignReader :: read( SP_XmlPullParser * parser, char c )
+{
+    if( '[' == c ) {
+        changeReader( parser, getReader( parser, SP_XmlReader::eCDataSection ) );
+    } else if( '-' == c ) {
+        changeReader( parser, getReader( parser, SP_XmlReader::eComment ) );
+    } else if( isupper( c ) ) {
+        SP_XmlReader * reader = getReader( parser, SP_XmlReader::eDocType );
+        reader->read( parser, c );
+        changeReader( parser, reader );
+    } else {
+        setError( parser, "not well-formed" );
+    }
+}
+
+SP_XmlPullEvent * SP_XmlSignReader :: getEvent( SP_XmlPullParser * parser )
+{
+    return NULL;
+}
+
+//=========================================================
+
+SP_XmlReaderPool :: SP_XmlReaderPool()
+{
+    mReaderList = (SP_XmlReader**)malloc( sizeof( void * ) * SP_XmlReader::MAX_READER );
+    memset( mReaderList, 0, sizeof( void * ) * SP_XmlReader::MAX_READER );
+}
+
+SP_XmlReaderPool :: ~SP_XmlReaderPool()
+{
+    for( int i = 0; i < SP_XmlReader::MAX_READER; i++ ) {
+        if( NULL != mReaderList[i] ) {
+            delete mReaderList[i];
+        }
+    }
+    free( mReaderList );
+}
+
+SP_XmlReader * SP_XmlReaderPool :: borrow( int type )
+{
+    SP_XmlReader * reader = NULL;
+
+    if( type >= 0 && type < SP_XmlReader::MAX_READER ) {
+        reader = mReaderList[ type ];
+        if( NULL == reader ) {
+            switch( type ) {
+            case SP_XmlReader::ePI: reader = new SP_XmlPIReader(); break;
+            case SP_XmlReader::eSTag: reader = new SP_XmlStartTagReader(); break;
+            case SP_XmlReader::eETag: reader = new SP_XmlEndTagReader(); break;
+            case SP_XmlReader::ePCData: reader = new SP_XmlPCDataReader(); break;
+            case SP_XmlReader::eCDataSection: reader = new SP_XmlCDataSectionReader(); break;
+            case SP_XmlReader::eComment: reader = new SP_XmlCommentReader(); break;
+            case SP_XmlReader::eDocType: reader = new SP_XmlDocTypeReader(); break;
+            case SP_XmlReader::eLBracket: reader = new SP_XmlLeftBracketReader(); break;
+            case SP_XmlReader::eSign: reader = new SP_XmlSignReader(); break;
+            }
+            mReaderList[ type ] = reader;
+        }
+    }
+
+    //printf( "\nborrow change: %s\n", typeid( *reader ).name() );
+
+    return reader;
+}
+
+void SP_XmlReaderPool :: save( SP_XmlReader * reader )
+{
+    //printf( "\nreturn change: %s\n", typeid( *reader ).name() );
+    reader->reset();
+}
+
+//=========================================================
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlreader.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __spxmlreader_hpp__
+#define __spxmlreader_hpp__
+
+class SP_XmlPullParser;
+class SP_XmlPullEvent;
+class SP_XmlStringBuffer;
+
+class SP_XmlReader {
+public:
+    enum { MAX_READER = 16 };
+    enum { ePI, eDocType, eSTag, eETag, ePCData,
+        eCDataSection, eComment, eLBracket, eSign };
+
+    /**
+     * @param  parser : act as reader's context
+     * @param  c : a char in xml stream
+     */
+    virtual void read( SP_XmlPullParser * parser, char c ) = 0;
+
+    /**
+     * reset reader state
+     */
+    virtual void reset();
+
+    /**
+     * convert internal xml string to event
+     * @return NULL : this reader don't generate any event or error occured
+     */
+    virtual SP_XmlPullEvent * getEvent( SP_XmlPullParser * parser ) = 0;
+
+protected:
+    SP_XmlStringBuffer * mBuffer;
+
+    friend class SP_XmlReaderPool;
+
+    SP_XmlReader();
+    virtual ~SP_XmlReader();
+
+    /// help to call parser->changeReader
+    void changeReader( SP_XmlPullParser * parser, SP_XmlReader * reader );
+
+    /// help to call parser->getReader
+    SP_XmlReader * getReader( SP_XmlPullParser * parser, int type );
+
+    /// help to call parser->setError
+    static void setError( SP_XmlPullParser * parser, const char * error );
+
+private:
+    SP_XmlReader( SP_XmlReader & );
+    SP_XmlReader & operator=( SP_XmlReader & );
+};
+
+class SP_XmlPIReader : public SP_XmlReader {
+public:
+    SP_XmlPIReader();
+    virtual ~SP_XmlPIReader();
+    virtual void read( SP_XmlPullParser * parser, char c );
+    virtual SP_XmlPullEvent * getEvent( SP_XmlPullParser * parser );
+
+private:
+    static SP_XmlPullEvent * parseDocDeclEvent( SP_XmlPullParser * parser,
+            SP_XmlStringBuffer * buffer );
+};
+
+class SP_XmlStartTagReader : public SP_XmlReader {
+public:
+    SP_XmlStartTagReader();
+    virtual ~SP_XmlStartTagReader();
+    virtual void read( SP_XmlPullParser * parser, char c );
+    virtual SP_XmlPullEvent * getEvent( SP_XmlPullParser * parser );
+    virtual void reset();
+
+private:
+    int mIsQuot;
+};
+
+class SP_XmlEndTagReader : public SP_XmlReader {
+public:
+    SP_XmlEndTagReader();
+    virtual ~SP_XmlEndTagReader();
+    virtual void read( SP_XmlPullParser * parser, char c );
+    virtual SP_XmlPullEvent * getEvent( SP_XmlPullParser * parser );
+};
+
+class SP_XmlPCDataReader : public SP_XmlReader {
+public:
+    SP_XmlPCDataReader();
+    virtual ~SP_XmlPCDataReader();
+    virtual void read( SP_XmlPullParser * parser, char c );
+    virtual SP_XmlPullEvent * getEvent( SP_XmlPullParser * parser );
+};
+
+class SP_XmlCDataSectionReader : public SP_XmlReader {
+public:
+    SP_XmlCDataSectionReader();
+    virtual ~SP_XmlCDataSectionReader();
+    virtual void read( SP_XmlPullParser * parser, char c );
+    virtual SP_XmlPullEvent * getEvent( SP_XmlPullParser * parser );
+};
+
+class SP_XmlCommentReader : public SP_XmlReader {
+public:
+    SP_XmlCommentReader();
+    virtual ~SP_XmlCommentReader();
+    virtual void read( SP_XmlPullParser * parser, char c );
+    virtual SP_XmlPullEvent * getEvent( SP_XmlPullParser * parser );
+};
+
+class SP_XmlDocTypeReader : public SP_XmlReader {
+public:
+    SP_XmlDocTypeReader();
+    virtual ~SP_XmlDocTypeReader();
+    virtual void read( SP_XmlPullParser * parser, char c );
+    virtual SP_XmlPullEvent * getEvent( SP_XmlPullParser * parser );
+};
+
+class SP_XmlLeftBracketReader : public SP_XmlReader {
+public:
+    SP_XmlLeftBracketReader();
+    virtual ~SP_XmlLeftBracketReader();
+    virtual void read( SP_XmlPullParser * parser, char c );
+    virtual SP_XmlPullEvent * getEvent( SP_XmlPullParser * parser );
+    virtual void reset();
+
+private:
+    int mHasReadBracket;
+};
+
+class SP_XmlSignReader : public SP_XmlReader {
+public:
+    SP_XmlSignReader();
+    virtual ~SP_XmlSignReader();
+    virtual void read( SP_XmlPullParser * parser, char c );
+    virtual SP_XmlPullEvent * getEvent( SP_XmlPullParser * parser );
+};
+
+class SP_XmlReaderPool {
+public:
+    SP_XmlReaderPool();
+    ~SP_XmlReaderPool();
+    SP_XmlReader * borrow( int type );
+    void save( SP_XmlReader * reader );
+
+private:
+    SP_XmlReader ** mReaderList;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlstag.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <typeinfo>
+
+#include "mstring.h"
+
+#include "spxmlstag.hpp"
+#include "spxmlutils.hpp"
+#include "spxmlevent.hpp"
+#include "spxmlcodec.hpp"
+
+SP_XmlSTagParser :: SP_XmlSTagParser( const char * encoding )
+{
+    mEvent = new SP_XmlStartTagEvent();
+    mReader = new SP_XmlSTagNameReader();
+    mStartTagName = new SP_XmlStringBuffer();
+    mError = NULL;
+
+    snprintf( mEncoding, sizeof( mEncoding ), "%s", encoding );
+}
+
+SP_XmlSTagParser :: ~SP_XmlSTagParser()
+{
+    if( NULL != mEvent ) delete mEvent;
+    mEvent = NULL;
+
+    if( NULL != mReader ) delete mReader;
+    mReader = NULL;
+
+    if( NULL != mStartTagName ) delete mStartTagName;
+    mStartTagName = NULL;
+
+    if( NULL != mError ) free( mError );
+    mError = NULL;
+}
+
+const char * SP_XmlSTagParser :: getEncoding()
+{
+    return mEncoding;
+}
+
+SP_XmlStartTagEvent * SP_XmlSTagParser :: takeEvent()
+{
+    SP_XmlStartTagEvent * event = mEvent;
+
+    mEvent = NULL;
+
+    return event;
+}
+
+const char * SP_XmlSTagParser :: getError()
+{
+    return mError;
+}
+
+void SP_XmlSTagParser :: changeReader( SP_XmlSTagReader * reader )
+{
+    delete mReader;
+    mReader = reader;
+}
+
+void SP_XmlSTagParser :: setError( const char * error )
+{
+    if( NULL != error ) {
+        if( NULL != mError ) free( mError );
+        mError = strdup( error );
+    }
+}
+
+void SP_XmlSTagParser :: append( const char * source, int len )
+{
+    for( int i = 0; i < len && NULL == mError; i++ ) {
+        mReader->read( this, source[ i ] );
+    }
+}
+
+//=========================================================
+
+SP_XmlSTagReader :: SP_XmlSTagReader()
+{
+    mBuffer = new SP_XmlStringBuffer();
+}
+
+SP_XmlSTagReader :: ~SP_XmlSTagReader()
+{
+    delete mBuffer;
+    mBuffer = NULL;
+}
+
+void SP_XmlSTagReader :: changeReader( SP_XmlSTagParser * parser,
+        SP_XmlSTagReader * reader )
+{
+    //printf( "\nchange: %s\n", typeid( *reader ).name() );
+    parser->changeReader( reader );
+}
+
+void SP_XmlSTagReader :: setError( SP_XmlSTagParser * parser, const char * error )
+{
+    parser->setError( error );
+}
+
+void SP_XmlSTagReader :: setName( SP_XmlSTagParser * parser, const char * name )
+{
+    parser->mEvent->setName( name );
+}
+
+void SP_XmlSTagReader :: addAttrName( SP_XmlSTagParser * parser, const char * name )
+{
+    parser->mStartTagName->append( name );
+}
+
+void SP_XmlSTagReader :: addAttrValue( SP_XmlSTagParser * parser, const char * value )
+{
+    SP_XmlStringBuffer decodeValue;
+    SP_XmlStringCodec::decode( parser->getEncoding(), value, &decodeValue );
+
+    parser->mEvent->addAttr( parser->mStartTagName->getBuffer(), decodeValue.getBuffer() );
+    parser->mStartTagName->clean();
+}
+
+//=========================================================
+
+SP_XmlSTagNameReader :: SP_XmlSTagNameReader()
+{
+}
+
+SP_XmlSTagNameReader :: ~SP_XmlSTagNameReader()
+{
+}
+
+void SP_XmlSTagNameReader :: read( SP_XmlSTagParser * parser, char c )
+{
+    if( isspace( c ) ) {
+        if( 0 == mBuffer->getSize() ) {
+            //leading space, skip
+        } else {
+            setName( parser, mBuffer->getBuffer() );
+            changeReader( parser, new SP_XmlSTagAttrNameReader() );
+        }
+    } else {
+        mBuffer->append( c );
+    }
+}
+
+//=========================================================
+
+SP_XmlSTagAttrNameReader :: SP_XmlSTagAttrNameReader()
+{
+    mWait4Quot = 0;
+}
+
+SP_XmlSTagAttrNameReader :: ~SP_XmlSTagAttrNameReader()
+{
+}
+
+void SP_XmlSTagAttrNameReader :: read( SP_XmlSTagParser * parser, char c )
+{
+    if( 1 == mWait4Quot ) {
+        if( '"' == c ) {
+            addAttrName( parser, mBuffer->getBuffer() );
+            changeReader( parser, new SP_XmlSTagEqualMarkReader() );
+        } else {
+            mBuffer->append( c );
+        }
+    } else {
+        if( isspace( c ) ) {
+            if( 0 == mBuffer->getSize() ) {
+                //leading space, skip
+            } else {
+                addAttrName( parser, mBuffer->getBuffer() );
+                changeReader( parser, new SP_XmlSTagEqualMarkReader() );
+            }
+        } else {
+            if( '"' == c && 0 == mBuffer->getSize() ) {
+                mWait4Quot = 1;
+            } else if( '=' == c ) {
+                addAttrName( parser, mBuffer->getBuffer() );
+                SP_XmlSTagReader * reader = new SP_XmlSTagEqualMarkReader();
+                changeReader( parser, reader );
+                reader->read( parser, c );
+            } else {
+                mBuffer->append( c );
+            }
+        }
+    }
+}
+
+//=========================================================
+
+SP_XmlSTagEqualMarkReader :: SP_XmlSTagEqualMarkReader()
+{
+}
+
+SP_XmlSTagEqualMarkReader :: ~SP_XmlSTagEqualMarkReader()
+{
+}
+
+void SP_XmlSTagEqualMarkReader :: read( SP_XmlSTagParser * parser, char c )
+{
+    if( isspace( c ) ) {
+        //skip
+    } else if( '=' == c ) {
+        changeReader( parser, new SP_XmlSTagAttrValueReader() );
+    } else {
+        addAttrValue( parser, "" );
+        SP_XmlSTagReader * reader = new SP_XmlSTagAttrNameReader();
+        changeReader( parser, reader );
+        reader->read( parser, c );
+
+        //setError( parser, "miss '=' between name & value" );
+    }
+}
+
+//=========================================================
+
+SP_XmlSTagAttrValueReader :: SP_XmlSTagAttrValueReader()
+{
+    mHasReadQuot = 0;
+}
+
+SP_XmlSTagAttrValueReader :: ~SP_XmlSTagAttrValueReader()
+{
+}
+
+void SP_XmlSTagAttrValueReader :: read( SP_XmlSTagParser * parser, char c )
+{
+    if( 0 == mHasReadQuot ) {
+        if( isspace( c ) ) {
+            //skip  
+        } else if( '"' == c ) {
+            mHasReadQuot = 1;
+        } else if( '\'' == c ) {
+            mHasReadQuot = 2;
+        } else {
+            setError( parser, "unknown attribute value start" );
+        }
+    } else {
+        if( ( 1 == mHasReadQuot && '"' == c ) 
+                || ( 2 == mHasReadQuot && '\'' == c ) ) {
+            addAttrValue( parser, mBuffer->getBuffer() );
+            changeReader( parser, new SP_XmlSTagAttrNameReader() );
+        } else {
+            mBuffer->append( c );
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlstag.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __spxmlstag_hpp__
+#define __spxmlstag_hpp__
+
+class SP_XmlSTagReader;
+class SP_XmlStartTagEvent;
+class SP_XmlStringBuffer;
+
+class SP_XmlSTagParser {
+public:
+    SP_XmlSTagParser( const char * encoding );
+    ~SP_XmlSTagParser();
+
+    void append( const char * source, int len );
+
+    SP_XmlStartTagEvent * takeEvent();
+    const char * getError();
+
+    const char * getEncoding();
+
+protected:
+    void changeReader( SP_XmlSTagReader * reader );
+    void setError( const char * error );
+
+    SP_XmlStartTagEvent * mEvent;
+
+    SP_XmlStringBuffer * mStartTagName;
+
+    friend class SP_XmlSTagReader;
+
+private:
+    SP_XmlSTagReader * mReader;
+    char * mError;
+    char mEncoding[ 32 ];
+};
+
+class SP_XmlSTagReader {
+public:
+    SP_XmlSTagReader();
+    virtual ~SP_XmlSTagReader();
+    virtual void read( SP_XmlSTagParser * parser, char c ) = 0;
+
+protected:
+
+    /// help to call parser->xxx
+    void changeReader( SP_XmlSTagParser * parser, SP_XmlSTagReader * reader );
+    void setError( SP_XmlSTagParser * parser, const char * error );
+
+    /// help to call parser->mEvent->xxx
+    void setName( SP_XmlSTagParser * parser, const char * name );
+    void addAttrName( SP_XmlSTagParser * parser, const char * name );
+    void addAttrValue( SP_XmlSTagParser * parser, const char * value );
+
+    SP_XmlStringBuffer * mBuffer;
+};
+
+class SP_XmlSTagNameReader : public SP_XmlSTagReader {
+public:
+    SP_XmlSTagNameReader();
+    virtual ~SP_XmlSTagNameReader();
+    virtual void read( SP_XmlSTagParser * parser, char c );
+};
+
+class SP_XmlSTagAttrNameReader : public SP_XmlSTagReader {
+public:
+    SP_XmlSTagAttrNameReader();
+    virtual ~SP_XmlSTagAttrNameReader();
+    virtual void read( SP_XmlSTagParser * parser, char c );
+
+private:
+    int mWait4Quot;
+};
+
+class SP_XmlSTagEqualMarkReader : public SP_XmlSTagReader {
+public:
+    SP_XmlSTagEqualMarkReader();
+    virtual ~SP_XmlSTagEqualMarkReader();
+    virtual void read( SP_XmlSTagParser * parser, char c );
+};
+
+class SP_XmlSTagAttrValueReader : public SP_XmlSTagReader {
+public:
+    SP_XmlSTagAttrValueReader();
+    virtual ~SP_XmlSTagAttrValueReader();
+    virtual void read( SP_XmlSTagParser * parser, char c );
+
+private:
+    int mHasReadQuot;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlutils.cpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#include "spxmlutils.hpp"
+
+//=========================================================
+
+const int SP_XmlArrayList::LAST_INDEX = -1;
+
+SP_XmlArrayList :: SP_XmlArrayList( int initCount )
+{
+    mMaxCount = initCount <= 0 ? 2 : initCount;
+    mCount = 0;
+    mFirst = (void**)malloc( sizeof( void * ) * mMaxCount );
+}
+
+SP_XmlArrayList :: ~SP_XmlArrayList()
+{
+    free( mFirst );
+    mFirst = NULL;
+}
+
+int SP_XmlArrayList :: getCount() const
+{
+    return mCount;
+}
+
+int SP_XmlArrayList :: append( void * value )
+{
+    if( NULL == value ) return -1;
+
+    if( mCount >= mMaxCount ) {
+        mMaxCount = ( mMaxCount * 3 ) / 2 + 1;
+        mFirst = (void**)realloc( mFirst, sizeof( void * ) * mMaxCount );
+        assert( NULL != mFirst );
+        memset( mFirst + mCount, 0, ( mMaxCount - mCount ) * sizeof( void * ) );
+    }
+
+    mFirst[ mCount++ ] = value;
+
+    return 0;
+}
+
+void * SP_XmlArrayList :: takeItem( int index )
+{
+    void * ret = NULL;
+
+    if( LAST_INDEX == index ) index = mCount -1;
+    if( index < 0 || index >= mCount ) return ret;
+
+    ret = mFirst[ index ];
+
+    mCount--;
+
+    if( ( index + 1 ) < mMaxCount ) {
+        memmove( mFirst + index, mFirst + index + 1,
+            ( mMaxCount - index - 1 ) * sizeof( void * ) );
+    } else {
+        mFirst[ index ] = NULL;
+    }
+
+    return ret;
+}
+
+const void * SP_XmlArrayList :: getItem( int index ) const
+{
+    const void * ret = NULL;
+
+    if( LAST_INDEX == index ) index = mCount - 1;
+    if( index < 0 || index >= mCount ) return ret;
+
+    ret = mFirst[ index ];
+
+    return ret;
+}
+
+void SP_XmlArrayList :: sort( int ( * cmpFunc )( const void *, const void * ) )
+{
+    for( int i = 0; i < mCount - 1; i++ ) {
+        int min = i;
+        for( int j = i + 1; j < mCount; j++ ) {
+            if( cmpFunc( mFirst[ min ], mFirst[ j ] ) > 0 ) {
+                min = j;
+            }
+        }
+
+        if( min != i ) {
+            void * temp = mFirst[ i ];
+            mFirst[ i ] = mFirst[ min ];
+            mFirst[ min ] = temp;
+        }
+    }
+}
+
+//=========================================================
+
+SP_XmlQueue :: SP_XmlQueue()
+{
+    mMaxCount = 8;
+    mEntries = (void**)malloc( sizeof( void * ) * mMaxCount );
+
+    mHead = mTail = mCount = 0;
+}
+
+SP_XmlQueue :: ~SP_XmlQueue()
+{
+    free( mEntries );
+    mEntries = NULL;
+}
+
+void SP_XmlQueue :: push( void * item )
+{
+    if( mCount >= mMaxCount ) {
+        mMaxCount = ( mMaxCount * 3 ) / 2 + 1;
+        void ** newEntries = (void**)malloc( sizeof( void * ) * mMaxCount );
+
+        unsigned int headLen = 0, tailLen = 0;
+        if( mHead < mTail ) {
+            headLen = mTail - mHead;
+        } else {
+            headLen = mCount - mTail;
+            tailLen = mTail;
+        }
+
+        memcpy( newEntries, &( mEntries[ mHead ] ), sizeof( void * ) * headLen );
+        if( tailLen ) {
+            memcpy( &( newEntries[ headLen ] ), mEntries, sizeof( void * ) * tailLen );
+        }
+
+        mHead = 0;
+        mTail = headLen + tailLen;
+
+        free( mEntries );
+        mEntries = newEntries;
+    }
+
+    mEntries[ mTail++ ] = item;
+    mTail = mTail % mMaxCount;
+    mCount++;
+}
+
+void * SP_XmlQueue :: pop()
+{
+    void * ret = NULL;
+
+    if( mCount > 0 ) {
+        ret = mEntries[ mHead++ ];
+        mHead = mHead % mMaxCount;
+        mCount--;
+    }
+
+    return ret;
+}
+
+void * SP_XmlQueue :: top()
+{
+    return mCount > 0 ? mEntries[ mHead ] : NULL;
+}
+
+//=========================================================
+
+SP_XmlStringBuffer :: SP_XmlStringBuffer()
+{
+    init();
+}
+
+void SP_XmlStringBuffer :: init()
+{
+    mSize = 0;
+    mMaxSize = 8;
+    mBuffer = (char*)malloc( mMaxSize );
+    memset( mBuffer, 0, mMaxSize );
+}
+
+SP_XmlStringBuffer :: ~SP_XmlStringBuffer()
+{
+    free( mBuffer );
+}
+
+int SP_XmlStringBuffer :: append( char c )
+{
+    if( mSize >= ( mMaxSize - 1 ) ) {
+        mMaxSize += ( mMaxSize * 3 ) / 2 + 1;
+        mBuffer = (char*)realloc( mBuffer, mMaxSize );
+        assert( NULL != mBuffer );
+        memset( mBuffer + mSize, 0, mMaxSize - mSize );
+    }
+    mBuffer[ mSize++ ] = c;
+
+    return 0;
+}
+
+int SP_XmlStringBuffer :: append( const char * value, int size )
+{
+    if( NULL == value ) return -1;
+
+    size = ( size <= 0 ? strlen( value ) : size );
+    if( size <= 0 ) return -1;
+
+    if( ( size + mSize ) > ( mMaxSize - 1 ) ) {
+        mMaxSize += size;
+        mBuffer = (char*)realloc( mBuffer, mMaxSize );
+        assert( NULL != mBuffer );
+        memset( mBuffer + mSize, 0, mMaxSize - mSize );
+    }
+
+    memcpy( mBuffer + mSize, value, size );
+    mSize += size;
+
+    return 0;
+}
+
+int SP_XmlStringBuffer :: getSize() const
+{
+    return mSize;
+}
+
+const char * SP_XmlStringBuffer :: getBuffer() const
+{
+    return mBuffer;
+}
+
+char * SP_XmlStringBuffer :: takeBuffer()
+{
+    char * ret = mBuffer;
+
+    mBuffer = NULL;
+    init();
+
+    return ret;
+}
+
+void SP_XmlStringBuffer :: clean()
+{
+    memset( mBuffer, 0, mMaxSize );
+    mSize = 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spxmlutils.hpp	Wed Nov 24 20:52:14 2010 +0000
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2007 Stephen Liu
+ * LGPL, see http://code.google.com/p/spxml/
+ * For license terms, see the file COPYING along with this library.
+ */
+
+#ifndef __spxmlutils_hpp__
+#define __spxmlutils_hpp__
+
+#include <stdio.h>
+
+typedef struct tagSP_XmlArrayListNode SP_XmlArrayListNode_t;
+
+class SP_XmlArrayList {
+public:
+    static const int LAST_INDEX;
+
+    SP_XmlArrayList( int initCount = 2 );
+    virtual ~SP_XmlArrayList();
+
+    int getCount() const;
+    int append( void * value );
+    const void * getItem( int index ) const;
+    void * takeItem( int index );
+    void sort( int ( * cmpFunc )( const void *, const void * ) );
+
+private:
+    SP_XmlArrayList( SP_XmlArrayList & );
+    SP_XmlArrayList & operator=( SP_XmlArrayList & );
+
+    int mMaxCount;
+    int mCount;
+    void ** mFirst;
+};
+
+class SP_XmlQueue {
+public:
+    SP_XmlQueue();
+    virtual ~SP_XmlQueue();
+
+    void push( void * item );
+    void * pop();
+    void * top();
+
+private:
+    void ** mEntries;
+    unsigned int mHead;
+    unsigned int mTail;
+    unsigned int mCount;
+    unsigned int mMaxCount;
+};
+
+class SP_XmlStringBuffer {
+public:
+    SP_XmlStringBuffer();
+    virtual ~SP_XmlStringBuffer();
+    int append( char c );
+    int append( const char * value, int size = 0 );
+    int getSize() const;
+    const char * getBuffer() const;
+    char * takeBuffer();
+    void clean();
+
+private:
+    SP_XmlStringBuffer( SP_XmlStringBuffer & );
+    SP_XmlStringBuffer & operator=( SP_XmlStringBuffer & );
+
+    void init();
+
+    char * mBuffer;
+    int mMaxSize;
+    int mSize;    
+};
+
+#ifdef WIN32
+
+#define snprintf _snprintf
+#define strncasecmp strnicmp
+#define strcasecmp  stricmp
+#endif
+
+#endif
+