RapidXml - A quick Xml parser/generator for C++

/* RapidXml.h

May need ASSERTs commenting out if you haven't got a suitable one defined.

---------------------------------------------------------------------

    Classes for parsing xml quickly. Example usage:

    bool ParseLibraryXml( wchar_t* xmlBuffer )
    {
        try
        {
            CDocument doc(xmlBuffer);
            CTag libraryTag = doc.GetRoot();

            if( ! libraryTag || libraryTag != L"Library" )
                return false; // Missing or incorrect root tag

            for( CTag child = root.BeginChild(); child; ++child )
            {
                if( child == L"Book" )
                {
                    ParseBookTag(child);
                }
                else
                {
                    // Skipping unexpected child tag
                }
            }

            return true;
        }
        catch( CParseError& e )
        {
            TRACE(e.what());
            return false;
        }
    }

    // Attributes can be parsed either by looping through them all:

    void ParseBookTag( CTag& bookTag )
    {
        assert( bookTag == L"Book" );

        CBook book;
        for( CAttribute a = bookTag.BeginAttributes(); a; ++a )
        {
            if( a == L"Title" )
            {
                book.title = a.GetValue();
            }
            else if( a == L"Author" )
            {
                book.author = a.GetValue();
            }
            else if( a == L"InPrint" )
            {
                book.inPrint = a.GetBool();
            }
            else if( a == L"PublishedDate" )
            {
                book.publishedDate = a.GetDateTime();
            }
        }

        GetLibrary().AddBook(book);
    }

    // Or by using the indexing operator:
    // If a default value isn't specified for the attribute being read, and
    // it doesn't exist, then a CValidationError will be thrown.
    // This will be slightly slower than iterating attributes (but not much!).

    void ParseBookTag( CTag& bookTag )
    {
        assert( bookTag == L"Book" );

        CBook book;
        book.title = a["Title"].GetValue();
        book.author = a["Author"].GetValue();
        book.inPrint = a["InPrint"].GetBool(false);
        book.publishedDate = a["PublishedDate"].GetDateTime();

        GetLibrary().AddBook(book);
    }

    When the param passed to a CDocument's constructor is a raw byte buffer,
    then the contents of this buffer will be modified! If it's constructed with a wstring,
    then an internal copy will be created and the original won't be changed.
    Characters within the document will be replaced by terminating
    nulls that split the data up into strings. GetName and GetValue
    return pointers to these strings from within the source xml buffer.
    If you keep pointers returned from GetName or GetValue, they
    will remain valid whilst you keep hold of the raw xml buffer.

    The classes do not handle:
      * Data that's not in an attribute eg. <tag>I'll cause a problem</tag><tag a="But, I'm ok"></tag>
      * Escaped data eg <tag a="Bad char here:&quot;"/>
      * Processing instructions (except for the first one in the doc)
      * Any encoding formats other than UTF-16
      * Comments <tag><!-- this will confuse RapidXml --></tag>
      * CDATA sections <tag a="<![CDATA[I'll cause havoc]]>"/>
      * namespaces
    You'll get undefined behaviour (probably a CParseError will be thrown)
    if the xml you want to parse contains any of these.

    Attributes for a tag may only be iterated over once. If operator [] is used
    to access attributes, they can't be iterated over becaues CTag will iterate
    over attibutes internally. operator [] can be called as often as liked to
    retrieve an attributes value.

    If you do a BeginChild to start iterating over children of a tag, then you MUST
    call operator ++ on the returned child Tag until you reach the end of the siblings.
    BeginChild may only be called once for a tag (this means an xml tag, not a CTag,
    if you copy a CTag...
        CTag b = a;
    ... you may not call BeginChild on both a and b.

    Basically... once you advance to the next tag (child or next sibling), you
    can only modify or advance from that tag. Any old CTag instances are only
    good for having their attributes and names read.

    A CDocument object must outlive all of the CTag and CAttributes used to
    access it.

    An xml document isn't checked for being well formed before parsing begins,
    so any call to operator ++ may result in a CParseError being thrown.

---------------------------------------------------------------------
*/

#pragma once
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdexcept>
#include <map>
#include <string>

namespace RapidXml {

// Forward defines
class CParseError;
class CDocument;
class CTag;
class CAttribute;

//////////////////////////////////////////////////////////////////////////////////
//
// Class: CRapidXmlException
//
// Base class for RapidXml exceptions. It's a good idea to wrap a
// CDocuments lifetime within a 'try catch' that catches and handles
// CRapidXmlException.
//
//////////////////////////////////////////////////////////////////////////////////
class CRapidXmlException : public std::runtime_error
{
public:
    CRapidXmlException(const std::string& message)
        : std::runtime_error(message)
    {
    }
};

//////////////////////////////////////////////////////////////////////////////////
//
// Class: CParseError
//
// Parse errors are generally thrown when the xml document being parsed
// is not 'well formed'.
//
//////////////////////////////////////////////////////////////////////////////////
class CParseError : public CRapidXmlException
{
public:
    CParseError(const std::string& message)
        : CRapidXmlException(message)
    {
    }
};

//////////////////////////////////////////////////////////////////////////////////
//
// Class: CValidationError
//
// Thrown when the xml is well formed, but isn't valid (wrong tags/attribs in it etc).
// It's suggested that one of these be thrown in user code if the tags found or not
// found in a document don't match the expected schema.
//
//////////////////////////////////////////////////////////////////////////////////
class CValidationError : public CRapidXmlException
{
public:
    CValidationError(const std::string& message)
        : CRapidXmlException(message)
    {
    }
};

//////////////////////////////////////////////////////////////////////////////////
//
// Function: SkipWhiteSpace
//
// Advances the data argument over any white space characters it's
// pointing at.
//
//////////////////////////////////////////////////////////////////////////////////
inline void SkipWhiteSpace(wchar_t*& data)
{
    for(;;)
    {
        switch( *data )
        {
        case L' ':
        case L'\n':
        case L'\r':
        case L'\t':
            ++data;
            break;
        default:
            return;
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////
//
// Function: SkipProcessingInstruction
//
// Advances the data argument over any xml processing instruction it's
// pointing at.
//
//////////////////////////////////////////////////////////////////////////////////
inline void SkipProcessingInstruction(wchar_t*& data)
{
    SkipWhiteSpace(data);

    if( 0 == wcsncmp(data,L"<?", 2 ) )
    {
        // skip processing instruction
        wchar_t* pos = wcsstr( data, L"?>");
        if( !pos )
        {
            ASSERT(!"Processing instruction not terminated");
            throw CParseError(__LOC__ " Processing instruction not terminated");
        }
        data = pos + 2;
        SkipWhiteSpace(data);
    }
}

//////////////////////////////////////////////////////////////////////////////////
//
// Class: CAttribute
//
//  Represents an xml attribute, allowing it's name and value to be
//  accessed. Also, the ++ operator and IsEnd method can be used
//  to iterate through all the attributes of the owning xml tag.
//
//////////////////////////////////////////////////////////////////////////////////
class CAttribute
{
public:

    operator bool () const
    {
        return ! IsEnd();
    }

    bool IsEnd() const
    {
        return m_data == 0;
    }

    wchar_t const* const GetName() const
    {
        ASSERT(!IsEnd());
        ASSERT(m_name);
        return m_name;
    }

    wchar_t const* const GetValue() const
    {
        if( IsEnd() )
        {
            ASSERT(!"Tried to get value of missing attribute");
            throw CValidationError( __LOC__ " Tried to get value of missing attribute");
        }

        ASSERT(m_value);
        return m_value;
    }

    wchar_t const* const GetValue(wchar_t const* const defaultValue) const
    {
        ASSERT(defaultValue);
        if( IsEnd() ) return defaultValue;
        else return GetValue();
    }

    unsigned short GetUI16() const
    {
        long value = GetI32();
        if( value < 0 || value > 0xFFFF )
        {
            throw CValidationError( __LOC__ "Tried to GetUI16, but attribute value was too large or too small" );
        }
        return static_cast<unsigned short>(value);
    }

    unsigned short GetUI16(unsigned short defaultValue) const
    {
        if( IsEnd() ) return defaultValue;
        else return GetUI16();
    }

    unsigned char GetUI8() const
    {
        long value = GetI32();
        if( value < 0 || value > 0xFF )
        {
            throw CValidationError( __LOC__ "Tried to GetUI8, but attribute value was too large or too small" );
        }
        return static_cast<unsigned char>(value);
    }

    unsigned char GetUI8(unsigned char defaultValue) const
    {
        if( IsEnd() ) return defaultValue;
        else return GetUI8();
    }

    long GetI32() const
    {
        return _wtol( GetValue() );
    }

    long GetI32(long defaultValue) const
    {
        if( IsEnd() ) return defaultValue;
        else return GetI32();
    }

    __int64 GetI64() const
    {
        return _wtoi64( GetValue() );
    }

    __int64 GetI64(__int64 defaultValue) const
    {
        if( IsEnd() ) return defaultValue;
        else return GetI64();
    }

    double GetDateTime() const
    {
        _variant_t date;
        if( PACUtils::XSDDateTimeToVariant(GetValue(),date) )
        {
            ASSERT( date.vt == VT_DATE );
            return date.date;
        }

        ASSERT(!"Attribute value not a valid date time value");
        throw CValidationError(__LOC__ " Attribute value not a valid date time value");
    }

    double GetDateTime(double defaultValue) const
    {
        if( IsEnd() ) return defaultValue;
        else return GetDateTime();
    }

    bool GetBool() const
    {
        if( 0 == wcscmp(GetValue(), L"1") )
            return true;
        else if( 0 == wcscmp(GetValue(), L"0") )
            return false;
        else if( 0 == wcscmp(GetValue(), L"true") )
            return true;
        else if( 0 == wcscmp(GetValue(), L"false") )
            return false;

        ASSERT(!"attribute value not a valid boolean value");
        throw CValidationError(__LOC__ " attribute value not a valid boolean value");
    }

    bool GetBool(bool defaultValue) const
    {
        if( IsEnd() ) return defaultValue;
        else return GetBool();
    }

    void operator ++ ()
    {
        ASSERT( !IsEnd() );
        Parse();
    }

    bool operator == ( wchar_t const * const name ) const
    {
        ASSERT( ! IsEnd() );
        return 0 == wcscmp(m_name,name);
    }

    bool operator != ( wchar_t const * const name ) const
    {
        return ! operator == (name);
    }

private:
    friend CTag;
    CAttribute(wchar_t* data)
        : m_data(data)
        , m_name(0)
        , m_value(0)
    {
        if( m_data )
            Parse();
    }

    // * Parses the next attribute.
    // * m_name and m_value will be set to point
    //   to the relevant null terminated strings of the parsed
    //   attribute.
    // * m_data will be moved past the attribute
    //   that's been parsed.
    //   If there are no more attributes m_data will be set to zero.
    // * If problems are encountered a CParseError may be thrown.
    void Parse()
    {
        ASSERT( m_data );

        SkipWhiteSpace(m_data);
        wchar_t* pos = wcschr(m_data,L'=');
        if( pos == 0 )
        {
            // End of attribs
            m_data = 0;
            return;
        }

        if( pos == m_data )
        {
            m_data = 0;
            ASSERT(!"Zero length attribute name");
            throw CParseError(__LOC__ "Zero length attribute name");
        }
        *pos = L'\0';
        m_name = m_data;

        ++pos;
        if( *pos != L'\"' )
        {
            m_data = 0;
            ASSERT(!"Attribute value doesn't start with a quote");
            throw CParseError(__LOC__ " Attribute value doesn't start with a quote" );
        }
        ++pos; // skip over quote

        m_value = pos;

        pos = wcschr(m_value,L'\"');
        if( pos == 0 )
        {
            m_data = 0;
            ASSERT(!"Didn't find end quote for value!");
            throw CParseError(__LOC__ "Didn't find end quote for value!");
        }
        *pos = L'\0';

        m_data = pos + 1;
    }

    wchar_t* m_data;
    wchar_t* m_name;
    wchar_t* m_value;
};

//////////////////////////////////////////////////////////////////////////////////
//
// Class: CTag
//
// Represents an xml tag.
//
// Use the ++ operator and IsEnd method to iterate through all siblings
// of the parent tag.
//
// Use BeginChild to get a CTag which can be used to iterate through
// the children of the tag. BeginChild may only be called once for a
// particular xml tag. If you do BeginChild for a tag called x then you
// MUST iterate to the end of x's children before moveing to x's next
// sibling (with ++x).
//
// Use BeginAttributes to access the tag's attributes. See class CAttribute
// BeginAttributes may only be called once for an xml tag.
//
//////////////////////////////////////////////////////////////////////////////////
class CTag
{
public:
    ~CTag()
    {
        // If do a BeginChild, must ++ to end of siblings.
        // Can possibly get an assertion here if you give up parsing a doc part
        // way through.
        ASSERT( ! m_parent || m_isEnd );
    }

    wchar_t const* const GetName() const
    {
        ASSERT(!m_isEnd);
        ASSERT(m_name);
        return m_name;
    }

    CTag BeginChild()
    {
        ASSERT( ! m_iteratedAllChildren );
        return CTag(m_data,this,m_isEnd || m_iteratedAllChildren);
    }

    void operator ++ ()
    {
        ASSERT( m_parent );
        if( ! m_iteratedAllChildren )
        {
            // There are children that must be skipped over
            for( CTag child = BeginChild(); !child.IsEnd(); ++child )
                ; // do nothing
        }

        m_isEnd = false;
        m_iteratedAllChildren = false;
        m_attribs = 0;
        m_name = 0;
        m_attribsMap.clear();
        Parse();
    }

    operator bool () const
    {
        return ! IsEnd();
    }

    bool IsEnd() const
    {
        return m_isEnd;
    }

    CAttribute BeginAttributes()
    {
        CAttribute attrib(m_attribs);
        m_attribs = 0; // don't allow client to parse through them again
        return attrib;
    }

    // Returns an attribute, the value of which can be read using one of CAttributes
    // getter methods. If the attribute does not exist under this tag, an empty attribute
    // will be returned. The empty attribute will throw a CValidationException if one of
    // its getter methods is called without supplying a default value.
    const CAttribute operator [] (wchar_t const* const name)
    {
        if( m_attribs )
        {
            ParseAttributes();
        }
        ASSERT( !m_attribs );

        std::map<std::wstring,CAttribute>::iterator lookup = m_attribsMap.find(name);

        if( lookup != m_attribsMap.end() )
        {
            return lookup->second;
        }
        else
        {
            return CAttribute(0);
        }
    }

    bool operator == ( wchar_t const * const name ) const
    {
        if( IsEnd() )
        {
            ASSERT(!"Trying to test name of IsEnd tag");
            throw CValidationError(__LOC__ " Trying to test name of IsEnd tag");
        }
        return 0 == wcscmp(m_name,name);
    }

    bool operator != ( wchar_t const * const name ) const
    {
        return ! operator == (name);
    }

    int GetDepth() const
    {
        int depth = 0;
        for( CTag* p = m_parent; p; p = p->m_parent )
            ++depth;
        return depth;
    }

private:
    friend CDocument;

    CTag( wchar_t*& data, CTag* parent, bool isEnd )
        : m_data(data)
        , m_name(0)
        , m_attribs(0)
        , m_parent(parent)
        , m_isEnd(isEnd)
        , m_iteratedAllChildren(false)

    {
        if( !isEnd )
            Parse();
    }

    void Parse()
    {
        ASSERT( m_data );
        ASSERT( ! m_name );
        ASSERT( ! m_isEnd );
        ASSERT( ! m_iteratedAllChildren );
        ASSERT( ! m_attribs );

        bool isBeginTag;
        bool isEndTag;
        m_name = ParseTag(m_data,isBeginTag,isEndTag,m_attribs);
        ASSERT( isBeginTag || isEndTag );
        if( isBeginTag && isEndTag )
        {
            m_iteratedAllChildren = true;
        }
        else if( !isBeginTag && isEndTag && 0 == wcscmp(m_name,m_parent->m_name) )
        {
            SetEnd();
        }
    }

    // Params:
    // [in/out] Reads the next tag from data, and advances data to past the tag.
    // [out] isBegin - true if the parsed tag is an xml begin tag. either <tag> or <tag/>
    // [out] isEnd - true if the parsed tag is an xml end tag. either </tag> or <tag/>
    //          Note <tag/> is xml shorthand for a begin and end in one tag
    // [out] attribs - will be set to point to the begining of any attribute data within the tag
    //          this attribute data will be null terminated.
    // [ret] the null terminated name of the parsed tag
    // Throws: CParseError if invalid (or xml not supported by this parser) is encountered.
    wchar_t* ParseTag( wchar_t*& data, bool& isBegin, bool& isEnd, wchar_t*& attribs )
    {
        wchar_t* name = L"";

        attribs = 0;
        isBegin = false;
        isEnd = false;

        SkipWhiteSpace(data);

        if( *data != L'<' )
        {
            ASSERT(!"No '<' to start tag");
            throw CParseError(__LOC__ " No '<' to start tag");
        }

        ++data;

        if( *data == L'/' )
        {
            ++data;

            // found tag of form </tag>
            isEnd = true;
            isBegin = false;

            size_t tagNameLen = wcscspn(data,L" >/");
            if( tagNameLen == 0 )
            {
                ASSERT(!"Tag name length is zero");
                throw CParseError(__LOC__ " Tag name length is zero" );
            }

            if( *(data + tagNameLen) != L'>' )
            {
                ASSERT(!"End tag shouldn't have any attribs");
                throw CParseError(__LOC__ " End tag shouldn't have any attribs" );
            }

            *(data + tagNameLen) = L'\0';
            name = data;
            data += tagNameLen + 1;

            return name;
        }
        else
        {
            isBegin = true;
            // isEnd not yet decided ... tag could still be of form <tag/>
        }

        size_t tagNameLen = wcscspn(data,L" >/");
        if( tagNameLen == 0 )
        {
            ASSERT(!"Tag name length is zero");
            throw CParseError(__LOC__ " Tag name length is zero" );
        }

        wchar_t tagEndChar = *(data + tagNameLen);

        if( tagEndChar == L'\0' )
        {
            ASSERT(!"Didn't find end of tag name before end of doc");
            throw CParseError(__LOC__ " Didn't find end of tag name before end of doc" );
        }

        *( data + tagNameLen ) = L'\0'; // null terminate tag name
        name = data;
        data += tagNameLen + 1;

        if( tagEndChar == L'>' )
        {
            // do nothing
        }
        else if( tagEndChar == L'/' )
        {
            // Move completely over the tag
            ASSERT( *data == L'>' );
            ++data;
            isEnd = true;
        }
        else if( tagEndChar == L' ' )
        {
            // tag has attributes
            // set out attribs param to be the start of them
            // and advance data over them
            attribs = data;

            wchar_t* pos = wcsstr( data, L">" );
            if( pos == 0 )
            {
                ASSERT(!"Didn't find end of tag");
                throw CParseError(__LOC__ " Didn't find end of tag" );
            }

            if( *(pos-1) == L'/' )
            {
                isEnd = true;
                *(pos-1) = L'\0';
            }

            *pos = L'\0';

            data = pos + 1;
        }
        else
        {
            ASSERT(FALSE); // should never get here
            throw CParseError(__LOC__);
        }

        return name;
    }

    void ParseAttributes()
    {
        ASSERT( m_attribs );

        for( CAttribute a = BeginAttributes(); a; ++a )
        {
            std::map<std::wstring,CAttribute>::value_type value =
                std::make_pair< std::wstring, CAttribute >( a.GetName(), a );

            std::pair< std::map<std::wstring,CAttribute>::iterator, bool > result =
                m_attribsMap.insert( value );

            if( ! result.second )
            {
                ASSERT(!"Tag has duplicate attributes");
                throw CParseError( __LOC__ " Tag has duplicate attributes");
            }
        }

        ASSERT( !m_attribs );
    }

    void SetEnd()
    {
        ASSERT( m_parent );
        m_isEnd = true;
        m_parent->m_iteratedAllChildren = true;
    }

    CTag* m_parent;
    wchar_t*& m_data;
    wchar_t* m_name;
    wchar_t* m_attribs;

    std::map<std::wstring,CAttribute> m_attribsMap;

    bool m_isEnd;
    bool m_iteratedAllChildren;
};

//////////////////////////////////////////////////////////////////////////////////
//
// Class: CDocument
//
//////////////////////////////////////////////////////////////////////////////////
class CDocument
{
public:
    CDocument(wchar_t* xmlData)
        : m_data(xmlData)
        , m_gotRoot(false)
        , m_buffer(0)
    {
        Initialise();
    }

    CDocument(std::wstring& xmlData)
        : m_data(0)
        , m_gotRoot(false)
        , m_buffer(0)
    {
        m_buffer = new wchar_t[xmlData.size()+1];
        std::copy(xmlData.begin(),xmlData.end(),m_buffer);
        m_buffer[xmlData.length()] = L'\0';

        m_data = m_buffer;

        Initialise();
    }

    ~CDocument()
    {
        delete [] m_buffer;
        m_buffer = 0;
    }

    CTag GetRoot()
    {
        ASSERT( !m_gotRoot ); // Can only call GetRoot once
        m_gotRoot = true;
        return CTag(m_data,0,false);
    }

private:
    void Initialise()
    {
        ASSERT( m_data );
        SkipProcessingInstruction(m_data);
    }

    bool m_gotRoot;
    wchar_t* m_data;
    wchar_t* m_buffer;
    bool m_ownsData;
};

//////////////////////////////////////////////////////////////////////////////////
//
// Class: CWTag     (W stands for write)
//
// Tag for writing xml.
//
// Notes:
//
// It's more efficient to write all of a tags attributes before adding child
// tags to it because attributes added after children can't be appended
// to the end of the xml document being built, but have to be insterted
// into the middle the document.
//
// A child tag must destruct before a second child tag is created. This
// usually works out ok because the scope for tags usually works out ok.
// But, say two child tags should be created in one function, then extra
// scopes may be required:
//
// void AddPartTags( CWTag& carTag, std::wstring engineModel, std::wstring registration )
// {
//      {
//         CWTag engineModelTag = carTag.AddTag(L"EngineModel");
//         engineModelTag.SetAttribute(L"name",engineModel.c_str());
//     }
//
//     {
//         CWTag registrationTag = carTag.AddTag(L"Registration");
//         registrationTag.SetAttribute(L"numberPlate",registration.c_str());
//     }
// }
//
// In this case though, this could be avoided by using the operator() overloads. This works
// because the tags are not given symbols - as temporary objects they only last for the lifetime
// of the expression (statement) - and that's what is required:
//
// void AddPartTags( CWTag& carTag, std::wstring engineModel, std::wstring registration )
// {
//      carTag(L"EngineModel") (L"name",engineModel.c_str());
//      carTag(L"Registration") (L"numberPlate",registration.c_str());
// }
//
//////////////////////////////////////////////////////////////////////////////////
class CWTag
{
public:
    CWTag( std::wstring& str, const wchar_t* const rootTagName )
        : m_str(str)
        , m_depth(0)
        , m_parent(0)
        , m_tagName(rootTagName)
        , m_distanceToEndOfAttributes(0)
        , m_deadTag(false)
        #if defined(DEBUG)
            , m_openChild(false)
        #endif
    {
        OpenTag();
    }

    ~CWTag()
    {
        if( m_deadTag )
        {
            // The tag object that this one got assigned to
            // took on the responsibility of closing the tag.
            return;
        }

        if( AttributesEnded() )
        {
            Indent();
            m_str += L"</";
            m_str += m_tagName;
            m_str += L">\r\n";
        }
        else
        {
            SetEndOfAttributes();
            m_str += L"/>\r\n";

            ASSERT( AttributesEnded() );
        }

        #if defined(DEBUG)
            if( m_parent )
            {
                m_parent->m_openChild = false;
            }
        #endif
    }

    CWTag( CWTag& v )
        : m_str(v.m_str)
        , m_depth(v.m_depth)
        , m_parent(v.m_parent)
        , m_tagName(v.m_tagName)
        , m_distanceToEndOfAttributes(v.m_distanceToEndOfAttributes)
        , m_deadTag(false)
        #if defined(DEBUG)
            , m_openChild(v.m_openChild)
        #endif
    {
        v.m_deadTag = true;
    }

    CWTag AddTag( const wchar_t* const name )
    {
        ASSERT( ! m_deadTag ); // Can't use a tag once it's been copied... only the copy is now valid.
        ASSERT( ! m_openChild ); // Can't add a child until the last one opened has destructed.

        if( ! AttributesEnded() )
        {
            SetEndOfAttributes();
            m_str += L">\r\n";

            ASSERT( AttributesEnded() );
        }

        #if defined(DEBUG)
        {
            m_openChild = true;
        }
        #endif

        return CWTag(m_str, this,name,m_depth+1);
    }

    CWTag& SetAttribute( const wchar_t* const name, const wchar_t* const value )
    {
        ASSERT( ! m_deadTag ); // Can't use a tag once it's been copied... only the copy is now valid.

        if( ! AttributesEnded() )
        {
            m_str += L" ";
            m_str += name;
            m_str += L"=\"";
            m_str += value;
            m_str += L"\"";
        }
        else
        {
            std::wstring attrib = L" ";
            attrib += name;
            attrib += L"=\"";
            attrib += value;
            attrib += L"\"";
            m_str.insert(AbsoluteEndOfAttributes(),attrib);
            m_distanceToEndOfAttributes += attrib.size();
        }
        return *this;
    }

    CWTag& SetAttribute( const wchar_t* const name, long value, int radix = 10 )
    {
        wchar_t strValue[128];
        _ltow(value,strValue,radix);
        return SetAttribute(name,strValue);
    }

    CWTag& SetAttribute( const wchar_t* const name, unsigned long value, int radix = 10 )
    {
        wchar_t strValue[128];
        _ultow(value,strValue,radix);
        return SetAttribute(name,strValue);
    }

    CWTag& SetAttribute( const wchar_t* const name, int value, int radix = 10 )
    {
        wchar_t strValue[128];
        _itow(value,strValue,radix);
        return SetAttribute(name,strValue);
    }

    CWTag& SetAttribute( const wchar_t* const name, __int64 value, int radix = 10 )
    {
        wchar_t strValue[128];
        _i64tow(value,strValue,radix);
        return SetAttribute(name,strValue);
    }

    CWTag& SetAttribute( const wchar_t* const name, unsigned __int64 value, int radix = 10 )
    {
        wchar_t strValue[128];
        _ui64tow(value,strValue,radix);
        return SetAttribute(name,strValue);
    }

    CWTag& SetAttribute( const wchar_t* const name, bool value )
    {
        return SetAttribute(name,static_cast<int>(value),10);
    }

    // Calls to Operator () can be chained in one expression for easy xml formatting.
    // Example:
    //      CWTag bookTag = libraryTag(L"Book") (L"author",authorName) (L"isbn",isbn) (L"borrower",borrower);
    //      bookTag(L"Review") (L"comments", L"very good");
    // results in the new xml being added under the <Library> tag:
    //      <Book author="Isaac Asimov" isbn="2938237" borrower="Mr. Blobby">
    //          <Review comments="very good"/>
    //      </Book>

    // Quick syntax for AddTag
    CWTag operator()( const wchar_t* const name ) { return AddTag(name); }
    // Quick syntax for SetAttribute
    CWTag& operator()( const wchar_t* const name, const wchar_t* const value ) { return SetAttribute(name,value); }
    CWTag& operator()( const wchar_t* const name, const long value, int radix = 10 ) { return SetAttribute(name,value,radix); }
    CWTag& operator()( const wchar_t* const name, const unsigned long value, int radix = 10 ) { return SetAttribute(name,value,radix); }
    CWTag& operator()( const wchar_t* const name, const int value, int radix = 10 ) { return SetAttribute(name,value,radix); }
    CWTag& operator()( const wchar_t* const name, const __int64 value, int radix = 10 ) { return SetAttribute(name,value,radix); }
    CWTag& operator()( const wchar_t* const name, const unsigned __int64 value, int radix = 10 ) { return SetAttribute(name,value,radix); 

}
    CWTag& operator()( const wchar_t* const name, const bool value ) { return SetAttribute(name,value); }

    bool AttributesEnded() const
    {
        ASSERT( ! m_deadTag ); // Can't use a tag once it's been copied... only the copy is now valid.
        return m_distanceToEndOfAttributes != 0;
    }

private:
    CWTag( std::wstring& str, CWTag* base, const wchar_t* const name, int depth )
        : m_str(str)
        , m_depth(depth)
        , m_parent(base)
        , m_tagName(name)
        , m_distanceToEndOfAttributes(0)
        , m_deadTag(false)
        #if defined(DEBUG)
        , m_openChild(false)
        #endif
    {
        OpenTag();
    }

    CWTag& operator=( CWTag& ) {} // Copy assignment not available

    void OpenTag()
    {
        Indent();
        m_str += L"<";
        m_str += m_tagName;
    }

    void Indent()
    {
        for( int i = 0; i < m_depth; ++i )
            m_str += L"\t";
    }

    size_t AbsoluteEndOfAttributes()
    {
        size_t pos = m_distanceToEndOfAttributes;
        for( CWTag* tag = m_parent; tag; tag = tag->m_parent )
        {
            pos += tag->m_distanceToEndOfAttributes;
        }
        return pos;
    }

    void SetEndOfAttributes()
    {
        m_distanceToEndOfAttributes = m_str.size() -
            (m_parent ? m_parent->AbsoluteEndOfAttributes() : 0);
    }

    CWTag* m_parent;
    #if defined(DEBUG)
        bool m_openChild;
    #endif
    int m_depth;
    std::wstring& m_str;
    const wchar_t* const m_tagName;

    // Character distance between the end of the attributes of the parent tag, and
    // the end of the attributes for this tag. Zero, if the end of the attributes in this
    // tag hasn't yet been reached.
    size_t m_distanceToEndOfAttributes;

    bool m_deadTag; // Tag becomes dead once it gets copied to another tag object.
};

} // namespace RapidXml

Leave a Reply