65.9K
CodeProject is changing. Read more.
Home

XML as a collection

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1 vote)

May 16, 2002

2 min read

viewsIcon

81381

downloadIcon

1914

A class for reading and writing XML files

Introduction

If you ever dreamed about a simple MFC-Based collection to handle an ANSI XML file then here is the solution.

CXXMLFile : (Class) eXtended XML File

This class reads simple XML files. It assumes the HTML/XML conventions in symbols and tags. But, the tag <? .... ?> is ignored for now. This means that no codepage support is available (to make code simpler).

When CXXMLFile reads a file it creates a tag tree with tree types of nodes:

 

Legend:

  1. CElementPart. Abstract base class for all node types. It includes the common member variable 'm_Text'.
  2. CText. Derived from CElementPart. It represents a piece of text, it can be surrounded by any kind of nodes. Symbol replacement will be applied here, for example &nbsp; in HTML code.
  3. CComment. A comment in form <!--something-->. It is not affected by symbol replacement.
  4. CElement. A tag. It has three properties: m_Text (the tag name), AttributeMap(A string map atname -> at value), and by inheritance a CList of Nodes.

Then, only a CElement node type should have children.

CXXMLFile Class Reference

List of all members.

Public Methods

CXXMLFile ()
Constructor.

virtual ~CXXMLFile ()
Destructor.

void RemoveAll ()
Delete all entries and make a default root node.

void AddSymbol (CString coded, CString decoded)
Adds a symbol.

void DefaultSymbols ()
Set default symbols.

void ClearSymbols ()
Clear symbols.

void AddOpenTag (CString tag)
Adds an open tag. (HTML ).

bool Write ()
Writes XML file.

CString GetFile ()
Get XML filename.

void SetFile (CString filename)
Set XML filename.

bool Read ()
The read XML function.

 

CElementPart * Root ()
Gets the root. (Create one if it's empty).

CElementPart * AddElement (CElementPart *Parent)
Adds a node type element.

CElementPart * AddComment (CElementPart *Parent, CString text)
Adds a comment node.

CElementPart * AddText (CElementPart *Parent, CString text)
Adds a text node.

void SetText (CElementPart *node, CString text)
Sets text property in a node.

void GetText (CElementPart *node, CString &text)
Gets text property in a node.

bool IsElement (CElementPart *node)
Determines wheter node is element.

bool IsComment (CElementPart *node)
Determines wheter node is comment.

bool IsText (CElementPart *node)
Determines wheter node is text.

CMapStringToString * GetElementAttrMap (CElementPart *node)
Returns a pointer to the attribute map of the element.

bool BuildChildList (CElementPart *node, CList< CElementPart *, CElementPart * > &l)
Builds a list of child nodes.

 

Public Attributes

CStringList m_ErrorList

Private Methods

void Decodify (CString &html)
Decodify using symbol table.

void Codify (CString &html)
Codify using symbol table.

void WritePart (CStdioFile *f, CElementPart *p, int Depth, bool bNoIdent=false)
Writes an XML node to a file (used by Write only).

Private Attributes

CElementPart * m_Root
CMapStringToString m_Symbols
CString m_Filename
CMapStringToString m_OpenTags

Header

// XXMLFile.h: interface for the CXXMLFile class.

//

//////////////////////////////////////////////////////////////////////


#if !defined(AFX_XXMLFILE_H__F5E3CD25_0B84_4191_A1A7_B3669180DFFA__INCLUDED_)
#define AFX_XXMLFILE_H__F5E3CD25_0B84_4191_A1A7_B3669180DFFA__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000


#include <afxtempl.h>



//************************************************************************//

/*!  \class CXXMLFile
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief A class to work with XML files as tree collections.
 */
class CXXMLFile  {
public:
    CXXMLFile();
    virtual ~CXXMLFile();
public:
    // Abstract node

    class CElementPart;
    class CElementPart{
    public:
        CElementPart* m_Parent;
        CString m_Text;
        enum TType { TElement, TText, TComment } m_Type;
    public:
        CElementPart(){};
        virtual ~CElementPart(){};
    };

    // Element node

    class CElement : public CElementPart, public CList<CElementPart*,CElementPart*>{
    public:
        CElement(){ m_Type=TElement; };
        virtual ~CElement();
    public:
        CMapStringToString AttributeToValue;
    };

    // Text node

    class CText : public CElementPart{
    public:
        CText(){ m_Type=TText; };
    };

    // Comment node

    class CComment : public CElementPart{
    public:
        CComment(){ m_Type=TComment; };
    };
public:
    // The error list, after executing Write() or Read() (if any)

    CStringList m_ErrorList;

    // Symbol table functions

    void RemoveAll();
    void AddSymbol(CString coded, CString decoded);
    void DefaultSymbols();
    void ClearSymbols();
    void AddOpenTag(CString tag);

    // File manipulation routines

    bool Write();
    CString GetFile();
    void SetFile(CString filename);
    bool Read();

    // Tree manipulation routines

    CElementPart* Root();
    CElementPart* AddElement(CElementPart* Parent);
    CElementPart* AddComment(CElementPart* Parent, CString text);
    CElementPart* AddText(CElementPart* Parent, CString text);
    void SetText(CElementPart* node, CString text);
    void GetText(CElementPart* node, CString &text);
    bool IsElement(CElementPart* node);
    bool IsComment(CElementPart* node);
    bool IsText(CElementPart* node);
    CMapStringToString* GetElementAttrMap(CElementPart* node);
    bool BuildChildList(CElementPart* node, CList<CElementPart*,CElementPart*> &l);
private:
    // The XML tree

    CElementPart* m_Root;

    // Symbol codification functions

    void Decodify(CString &html);
    void Codify(CString &html);
    CMapStringToString m_Symbols; // encoded to decoded


    // Part management

    void WritePart(CStdioFile *f, CElementPart * p, int Depth, bool bNoIdent=false);

    // Filename

    CString m_Filename;

    // Open tags map (acts like a set, so second value is ignored)

    CMapStringToString m_OpenTags;
};

#endif // !defined(AFX_XXMLFILE_H__F5E3CD25_0B84_4191_A1A7_B3669180DFFA__INCLUDED_)

Source

// XXMLFile.cpp: implementation of the CXXMLFile class.

//

//////////////////////////////////////////////////////////////////////


#include "stdafx.h"

#include "XXMLFile.h"


#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////

// Construction/Destruction

//////////////////////////////////////////////////////////////////////



//************************************************************************//

/*!  \fn CXXMLFile::CXXMLFile()
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Constructor
 */
CXXMLFile::CXXMLFile()
{
    // The root is null

    m_Root=NULL;

    // clean tables, and create a default root node

    RemoveAll();

    // set default symbols and open tags

    DefaultSymbols();
}


//************************************************************************//

/*!  \fn CXXMLFile::~CXXMLFile()
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Destructor
 */
CXXMLFile::~CXXMLFile()
{
    // clean up root (and then, the full tree)

    if(m_Root!=NULL) delete m_Root;
}


//************************************************************************//

/*!  \fn CXXMLFile::CElement::~CElement()
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Element destructor (clean up the list of elements)
 */
CXXMLFile::CElement::~CElement(){
    // You must clean up the possible list of elements

    while(!IsEmpty()) delete RemoveHead();
};


//************************************************************************//

/*!  \fn bool IsSeparator(char ch){
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Ask for an XML separator 
 */
static bool IsSeparator(char ch){
    switch (ch){
    case ' ': return true;
    case '\t': return true;
    case '\r': return true;
    case '\n': return true;
    default: return false;
    };
};

//************************************************************************//

/*!  \fn bool HopSeparators(CString &html, int &pos,int FileRow)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief For internal use (Read()). It jumps over separators in a string by 
 * incrementing an integer pointer.
 */
static bool HopSeparators(CString &html, int &pos,int FileRow){
    while( (pos<html.GetLength()) && (IsSeparator(html.GetAt(pos))) ){
        if(html.GetAt(pos)=='\n') 
            FileRow++;
        pos++;
    }
    if(pos>=html.GetLength()) return false; else return true;
};

//************************************************************************//

/*!  \fn int CountChars(CString s,char ch)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief A brute-force character counter.
 */
static int CountChars(CString s,char ch){
    return s.Replace(CString()+ch,"");
};

//************************************************************************//

/*!  \fn int FindChars(CString &html, int pos, LPCTSTR chars)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief For internal use (Read()). It secuentially look for any char in 
 * a given set from an starting pointer in a string.
 */
static int FindChars(CString &html, int pos, LPCTSTR chars){
    int tmp=pos;
    CString seps = chars;
    while(tmp<html.GetLength()) {
        if(seps.FindOneOf( CString(html.GetAt(tmp)) )!=-1){
            return tmp;
        }
        tmp++;
    }
    return -1;
};


//************************************************************************//

/*!  \fn bool CXXMLFile::Read()
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief The read XML function.
 */
bool CXXMLFile::Read()
{
    CString filename = m_Filename;
    int FileRow=1;

    // Init Tree

    if(m_Root!=NULL) delete m_Root;
    CElementPart** crut = &m_Root;
    CElement* Element = new CElement();
    Element->m_Parent=(*crut);
    (*crut)=Element;
    ((CElement*)(*crut))->m_Text = "?root?";

    // Clean up errors

    m_ErrorList.RemoveAll();

    // A brute-force reading of a text file (the most faster)

    CString html;
    TRY{
        CFile * f = new CFile(filename,CFile::modeRead|CFile::shareDenyNone);
        if(f==NULL) ::AfxThrowFileException(CFileException::fileNotFound,-1,filename);
        html.Empty();
        char * buf = html.GetBufferSetLength(f->GetLength());
        f->SeekToBegin();
        f->Read(buf,f->GetLength());
        f->Close();
        delete f;
        html.ReleaseBuffer();
        html.FreeExtra();
        html.Replace("\r\n","\n");
    }CATCH_ALL(e){
        m_ErrorList.AddTail("Error: File not found.");
        return false;
    }END_CATCH_ALL

    int p1,p2,p3,p4;
    p1=p2=p3=p4=0;
    while(true){
        if(p1>=html.GetLength()) return true;
        p2 = html.Find('<',p1);
        if(p2==-1){
            m_ErrorList.AddTail("Warning at line "+CString(FileRow)+
                ": There's some text before EOF, ignoring.");
            return true;
        } 
        CString text = html.Mid(p1,p2-p1);
        Decodify(text);
        if(!text.IsEmpty()){
            if( (*crut)==NULL ){
                m_ErrorList.AddTail("Warning at line "+CString(FileRow)
                +": No tag active but text found, must be at the start, ignoring.");
            } else {
                CText * t = new CText();
                t->m_Text = text;
                ((CElement*)(*crut))->AddTail( t );
                FileRow+=CountChars(text,'\n');
            }
        }

        // tag part

        p1=p2+1;
        if(p1>=html.GetLength()){ 
            m_ErrorList.AddTail("Error at line "+CString(FileRow)+
                ": Tag started buf EOF found.");
            return false;
        } else
        if(html.Mid(p1,3)=="!--"){ // comment

            p1=p1+3;
            p2 = html.Find("-->",p1);
            CString text = html.Mid(p1,p2-p1);
            if(p2==-1){
                m_ErrorList.AddTail("Error at line "+CString(FileRow)+
                    ": Comment tag unclosed.");
                return false;
            }
            if( (*crut)==NULL ){
                m_ErrorList.AddTail("Warning at line "+CString(FileRow)+
                    ": No tag active but text found, ignoring.");
            } else {
                CComment * t = new CComment();
                t->m_Text = text;
                ((CElement*)(*crut))->AddTail( t );
                FileRow+=CountChars(text,'\n');
            }
            p1=p2+=3;
        } else 
        if(html.GetAt(p1)=='?'){ // ?xml or something to avoid

            p2 = html.Find("?>",p1+1);
            if(p2==-1){
                m_ErrorList.AddTail("Error at line "+CString(FileRow)+
                    ": Tag <? unclosed.");
                return false;
            } else {
                p1=p2+2;
                continue;
            }
        } else 
        if(html.GetAt(p1)=='/'){ // an end tag

            p2 = html.Find('>',p1);
            if(p2==-1){
                m_ErrorList.AddTail("Error at line "+CString(FileRow)+
                    ": Tag unclosed.");
                return false;
            }
            p1++;
            CString tagname = html.Mid(p1,p2-p1);
            tagname.TrimLeft();
            tagname.TrimRight();
            if( (*crut)==NULL ){
                m_ErrorList.AddTail("Error at line "+CString(FileRow)+
                    ": Closing tag when no tag???.");
                return false;
            }
            if( (*crut)->m_Text!=tagname ){
                m_ErrorList.AddTail("Error at line "+CString(FileRow)+
                    ": Closing tag differs from open tag.");
                return false;
            }
            (*crut)=(*crut)->m_Parent;
            p1=p2+1;
        } else { // a start tag

            // < _ tag ...

            if(!HopSeparators(html,p1,FileRow)){
                m_ErrorList.AddTail("Error at line "+CString(FileRow)+
                    ": Unspected EOF.");
                return false;
            }
            // < tag _ ...

            p2=FindChars(html,p1," \t\r\n>");
            if(p2==-1){
                m_ErrorList.AddTail("Error at line "+CString(FileRow)+
                    ": Unspected EOF.");
                return false;
            }
            CString tag = html.Mid(p1,p2-p1);             
            CElement* Element = new CElement();
            Element->m_Parent=(*crut);
            ((CElement*)(*crut))->AddTail( ((CElementPart*)Element) );
            (*crut)=Element;
            ((CElement*)(*crut))->m_Text = tag;
            p1=p2;
            while(true){
                // _ >... 

                // _ value="...

                if(!HopSeparators(html,p1,FileRow)){
                    m_ErrorList.AddTail("Error at line "+
                        CString(FileRow)+": Unspected EOF.");
                    return false;
                }
                if(html.GetAt(p1)=='>'){
                    // _ >... 

                    p1+=1;
                    tag.MakeLower(); // tag string will be no longer used

                    CString value;
                    if(m_OpenTags.Lookup(tag,value)){
                        // It's an open tag, that means that it will never be closed

                        // Eg: <br> in HTML

                        (*crut)=(*crut)->m_Parent; // closing node

                    }
                    break;
                };
                if(html.Mid(p1,2)=="/>"){
                    // _ />... 

                    // This kind of tag means that no close tag is expected.

                    p1+=2;
                    (*crut)=(*crut)->m_Parent; // collapse node

                    break;
                };
                // _ value="...

                p2=html.Find("=\"",p1);
                if(p2==-1){
                    m_ErrorList.AddTail("Error at line "+CString(FileRow)+
                        ": Unspected value form.");
                    return false;
                }
                CString valname = html.Mid(p1,p2-p1);
                valname.TrimLeft();
                valname.TrimRight();
                p1=p2+2;
                CString value;
                while(true){
                    if(p1>=html.GetLength()){
                        m_ErrorList.AddTail("Error at line "+
                            CString(FileRow)+": Unspected EOF.");
                        return false;
                    } else
                    if(html.GetAt(p1)=='\"'){
                        p1++;
                        break;
                    } else
                    if(html.Mid(p1,2)=="\\\""){
                        value+="\"";
                        p1+=2;
                    } else {
                        value+=html.GetAt(p1);
                        p1++;
                    }
                }
                Element->AttributeToValue[valname]=value;
            }
        }
    }
}


//************************************************************************//

/*!  \fn void CXXMLFile::SetFile(CString filename)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Set XML filename.
 */
void CXXMLFile::SetFile(CString filename)
{
    m_Filename=filename;
}


//************************************************************************//

/*!  \fn CString CXXMLFile::GetFile()
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Get XML filename.
 */
CString CXXMLFile::GetFile()
{
    return m_Filename;
}


//************************************************************************//

/*!  \fn void CXXMLFile::WritePart(CStdioFile *f, CXXMLFile::CElementPart * p, 
 *                                 int Depth, bool bNoIdent){
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Writes an XML node to a file (used by Write only).
 *
 *   \param f The target file.
 *   \param p The tree node.
 *   \param Depth The node depth (for identation).
 *   \param bNoIdent Boolean value used to cancel identation if the node is alone,
 * that is, without children and siblings.
 */
void CXXMLFile::WritePart(CStdioFile *f, CElementPart * p, int Depth, bool bNoIdent){
    int j;
    if(p->m_Type==CElementPart::TElement){
        {// Write identation

            // Avoid line-feed at the begining of the file

            if(f->GetPosition()!=0) 
                f->WriteString("\n"); 
            // the Identation with spaces

            for(j=0;j<Depth;j++) 
                f->WriteString(" ");
        }
        // Write tag header

        f->WriteString("<"+p->m_Text);

        // Write values

        POSITION pos = ((CElement*)p)->AttributeToValue.GetStartPosition();
        while(pos!=NULL){
            CString AtName,AtValue;
            ((CElement*)p)->AttributeToValue.GetNextAssoc(pos,AtName,AtValue);
            f->WriteString(" "+AtName+"=\""+AtValue+"\"");
        };

        // If the Element does not have subelements, sub-texts, or sub-comments

        // write it closed

        if(((CElement*)p)->IsEmpty()) {
            // Is an open tag (like HTML <br>)?

            CString tag;
            tag = p->m_Text;
            tag.MakeLower();
            CString value;
            if(m_OpenTags.Lookup(tag,value)){
                // It's an open tag, that means that it will 

                // never be closed Eg: <br> in HTML

                f->WriteString(">");
            } else {
                // Not an open tag

                f->WriteString("/>");
            }
        } else {
            f->WriteString(">");

            // Optimization to block ident on single subtext in element.

            bool NoIdent = false;
            if(((CElement*)p)->GetCount()==1)
                NoIdent=true;

            // For each element...

            pos = ((CElement*)p)->GetHeadPosition();
            while(pos!=NULL){
                CElementPart*e = ((CElement*)p)->GetAt(pos);
                WritePart(f,e,Depth+1,NoIdent);
                ((CElement*)p)->GetNext(pos);
            }

            // Optimization to block ident on single subtext in element.

            if(!NoIdent){
                {// Write identation

                    // Avoid line-feed at the begining of the file

                    if(f->GetPosition()!=0) 
                        f->WriteString("\n"); 
                    // the Identation with spaces

                    for(j=0;j<Depth;j++) 
                        f->WriteString(" ");
                }            
            }

            // Close tag

            f->WriteString("</"+p->m_Text+">");
        }
    } else 
    if(p->m_Type==CElementPart::TComment){
        // Write comment AS-IS

        {// Write identation

            // Avoid line-feed at the begining of the file

            if(f->GetPosition()!=0) 
                f->WriteString("\n"); 
            // the Identation with spaces

            for(j=0;j<Depth;j++) 
                f->WriteString(" ");
        }
        f->WriteString("<!--"+p->m_Text+"-->");
    } else 
    if(p->m_Type==CElementPart::TText){
        // Write text if it's not empty (an empty text can have sparators)

        CString empty_string = p->m_Text;
        empty_string.Replace('\n',' ');
        empty_string.Replace('\r',' ');
        empty_string.Replace('\t',' ');
        while(0!=empty_string.Replace("  "," "));
        if((!empty_string.IsEmpty())&&(empty_string!=" ")) {
            if(!bNoIdent){
                {// Write identation

                    // Avoid line-feed at the begining of the file

                    if(f->GetPosition()!=0) 
                        f->WriteString("\n"); 
                    // the Identation with spaces

                    for(j=0;j<Depth;j++) 
                        f->WriteString(" ");
                }
            }

            // The string must be written with symbol codification

            CString text = p->m_Text;
            Codify(text);
            f->WriteString(text);
        }
    } 
};


//************************************************************************//

/*!  \fn bool CXXMLFile::Write()
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Writes XML file.
 */
bool CXXMLFile::Write()
{
    m_ErrorList.RemoveAll();

    // check tree

    if(m_Root==NULL) {
        m_ErrorList.AddTail("Error: NULL tree.");
        return false;
    }
    if(m_Root->m_Type!=CElementPart::TElement) {
        m_ErrorList.AddTail("Error: tree root is not an Element.");

        return false; // root must be TElement

    }
    if(((CElement*)m_Root)->m_Text!="?root?") {
        m_ErrorList.AddTail("Error: tree root is not named ?root?");
        return false; // bad-written root

    }

    // Ok write file;

    CStdioFile * f = new CStdioFile(m_Filename,CFile::modeCreate|
            CFile::modeWrite|CFile::shareDenyNone|CFile::typeText);
    if(f==NULL){
        m_ErrorList.AddTail("Error: cannot open '"+m_Filename+"' for writing.");
        return false; 
    } 

    CElement * root = ((CElement*)m_Root);
    POSITION pos = root->GetHeadPosition();
    while(pos!=NULL){
        CElementPart * p = root->GetAt(pos);
        WritePart(f,p,0);
        root->GetNext(pos);
    }

    f->Close();
    delete f;

    return true;
}


//************************************************************************//

/*!  \fn void CXXMLFile::DefaultSymbols()
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Set default symbols.
 */
void CXXMLFile::DefaultSymbols()
{
    AddSymbol("&lt;","<");
    AddSymbol("&gt;",">");
    AddSymbol("&quot;","\"");
    AddSymbol(" "," ");
    AddSymbol("&apos;","'");
    AddSymbol("&amp;","&");
    m_OpenTags["br"];
}


//************************************************************************//

/*!  \fn void CXXMLFile::ClearSymbols()
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Clear symbols.
 */
void CXXMLFile::ClearSymbols()
{
    // Clear symbol table

    m_Symbols.RemoveAll();

    // Remove open tags

    m_OpenTags.RemoveAll();
}


//************************************************************************//

/*!  \fn void CXXMLFile::AddSymbol(CString coded, CString decoded)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Adds a symbol.
 */
void CXXMLFile::AddSymbol(CString coded, CString decoded)
{
    m_Symbols[coded]=decoded;
}


//************************************************************************//

/*!  \fn void CXXMLFile::RemoveAll()
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Delete all entries and make a default root node.
 */
void CXXMLFile::RemoveAll()
{
    // Clear symbol table

    ClearSymbols();

    // Remove open tags

    m_OpenTags.RemoveAll();

    // Clear tree and create the default main node

    if(m_Root!=NULL) delete m_Root;
    CElementPart** crut = &m_Root;
    CElement* Element = new CElement();
    Element->m_Parent=(*crut);
    (*crut)=Element;
    ((CElement*)(*crut))->m_Text = "?root?";
}


//************************************************************************//

/*!  \fn void CXXMLFile::Codify(CString &html)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Codify using symbol table.
 */
void CXXMLFile::Codify(CString &html)
{
    int pos = 0;
    while(pos<html.GetLength()){
        POSITION p = m_Symbols.GetStartPosition();
        while(p!=NULL){
            CString coded, decoded;
            m_Symbols.GetNextAssoc(p,coded,decoded);
            if((pos+decoded.GetLength())<=html.GetLength()){
                if(html.Mid(pos,decoded.GetLength())==decoded){
                    html = html.Left(pos) + coded + 
                        html.Mid(pos+decoded.GetLength());
                    pos = pos + coded.GetLength()-1;
                    break;
                }
            }
        }
        pos++;
    }
}


//************************************************************************//

/*!  \fn void CXXMLFile::Decodify(CString &html)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Decodify using symbol table.
 */
void CXXMLFile::Decodify(CString &html)
{
    int pos = 0;
    while(pos<html.GetLength()){
        POSITION p = m_Symbols.GetStartPosition();
        while(p!=NULL){
            CString coded, decoded;
            m_Symbols.GetNextAssoc(p,coded,decoded);
            if((pos+coded.GetLength())<=html.GetLength()){
                if(html.Mid(pos,coded.GetLength())==coded){
                    html = html.Left(pos) + decoded + 
                        html.Mid(pos+coded.GetLength());
                    pos = pos + decoded.GetLength()-1;
                    break;
                }
            }
        }
        pos++;
    }
}

//************************************************************************//

/*!  \fn void CXXMLFile::AddOpenTag(CString tag)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Adds an open tag. (HTML <br>)
 */
void CXXMLFile::AddOpenTag(CString tag){
    tag.MakeLower();
    m_OpenTags[tag];
}

//************************************************************************//

/*!  \fn CXXMLFile::CElementPart* CXXMLFile::Root()
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Gets the root. (Create one if it's empty).
 */
CXXMLFile::CElementPart* CXXMLFile::Root(){
    if(m_Root!=NULL) 
        return (CXXMLFile::CElementPart*)m_Root;
    CElementPart** crut = &m_Root;
    CElement* Element = new CElement();
    Element->m_Parent=(*crut);
    (*crut)=Element;
    ((CElement*)(*crut))->m_Text = "?root?";
    return Element;
}

//************************************************************************//

/*!  \fn CXXMLFile::CElementPart* CXXMLFile::AddElement(CElementPart* Parent)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Adds a node type element
 */
CXXMLFile::CElementPart* CXXMLFile::AddElement(CElementPart* Parent){
    // Only TElement nodes support childs

    if(Parent->m_Type!=CXXMLFile::CElementPart::TElement)
        return NULL;
    CXXMLFile::CElement * elem = (CXXMLFile::CElement*)Parent;    
    CXXMLFile::CElement * new_elem = new CElement();
    elem->AddTail( ((CXXMLFile::CElementPart*)new_elem) );
    return new_elem;
}


//************************************************************************//

/*!  \fn void CXXMLFile::SetText(CElementPart* node, CString text)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Sets text property in a node.
 */
void CXXMLFile::SetText(CElementPart* node, CString text){
    node->m_Text = text;
};

//************************************************************************//

/*!  \fn void CXXMLFile::GetText(CElementPart* node, CString &text)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Gets text property in a node.
 */
void CXXMLFile::GetText(CElementPart* node, CString &text){
    text = node->m_Text;
};

//************************************************************************//

/*!  \fn CXXMLFile::CElementPart* CXXMLFile::AddComment(CElementPart* Parent, 
 *                                                      CString text)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Adds a comment node
 */
CXXMLFile::CElementPart* CXXMLFile::AddComment(CElementPart* Parent, CString text){
    // Only TElement nodes support childs

    if(Parent->m_Type!=CXXMLFile::CElementPart::TElement)
        return NULL;
    CXXMLFile::CElement * elem = (CXXMLFile::CElement*)Parent;    
    CXXMLFile::CComment * new_elem = new CComment();
    elem->AddTail( ((CXXMLFile::CElementPart*)new_elem) );
    return new_elem;
};

//************************************************************************//

/*!  \fn CXXMLFile::CElementPart* CXXMLFile::AddText(CElementPart* Parent,
 *                                                   CString text)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Adds a text node
 */
CXXMLFile::CElementPart* CXXMLFile::AddText(CElementPart* Parent, CString text){
    // Only TElement nodes support childs

    if(Parent->m_Type!=CXXMLFile::CElementPart::TElement)
        return NULL;
    CXXMLFile::CElement * elem = (CXXMLFile::CElement*)Parent;    
    CXXMLFile::CText * new_elem = new CText();
    elem->AddTail( ((CXXMLFile::CElementPart*)new_elem) );
    return new_elem;
}

//************************************************************************//

/*!  \fn bool CXXMLFile::IsElement(CElementPart* node)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief  Determines wheter node is element. 
 */
bool CXXMLFile::IsElement(CElementPart* node)
{
    return (node->m_Type==CXXMLFile::CElementPart::TElement);
}

//************************************************************************//

/*!  \fn bool CXXMLFile::IsComment(CElementPart* node)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Determines wheter node is comment. 
 */
bool CXXMLFile::IsComment(CElementPart* node)
{
    return (node->m_Type==CXXMLFile::CElementPart::TComment);
}

//************************************************************************//

/*!  \fn bool CXXMLFile::IsText(CElementPart* node)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Determines wheter node is text. 
 */
bool CXXMLFile::IsText(CElementPart* node)
{
    return (node->m_Type==CXXMLFile::CElementPart::TText);
}

//************************************************************************//

/*!  \fn CMapStringToString* CXXMLFile::GetElementAttrMap(CElementPart* node)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Returns a pointer to the attribute map of the element.
 */
CMapStringToString* CXXMLFile::GetElementAttrMap(CElementPart* node)
{
    // Only TElement nodes support childs

    if(node->m_Type!=CXXMLFile::CElementPart::TElement)
        return NULL;
    CXXMLFile::CElement * elem = (CXXMLFile::CElement*)node;    
    return &elem->AttributeToValue;
}

//************************************************************************//

/*!  \fn bool CXXMLFile::BuildChildList(CElementPart* node, 
 *                                      CList<CElementPart*,CElementPart*> &l)
 *   \author Manuel Lucas Viñas Livschitz (MLVL)
 *   \date 04/05/2002
 *   \brief Builds a list of child nodes.
 */
bool CXXMLFile::BuildChildList(CElementPart* node, 
                               CList<CElementPart*,CElementPart*> &l)
{
    // Only TElement nodes support childs

    if(node->m_Type!=CXXMLFile::CElementPart::TElement)
        return false;
    l.RemoveAll();
    l.AddTail( ((CElementPart*)node) );
    return true;
}

Example

The most simple example is in trying to load an XML file, converting in a CXMLFile collection and then writing back an XML file.

We will do the test with books.xml (from MSDN XML SDK)

Source code (extract)

// XMLTest.cpp : Defines the entry point for the console application.

//


#include "stdafx.h"

#include "XMLTest.h"


....

// read-write test

        CXXMLFile xml;
        xml.SetFile("books.xml");
        bool bok = xml.Read();

        if(!bok){
            POSITION pos = xml.m_ErrorList.GetHeadPosition();
            while(pos!=NULL){
                printf(xml.m_ErrorList.GetAt(pos)+"\n");
                xml.m_ErrorList.GetNext(pos);
            }
        } else {
            xml.SetFile("books_out.xml");
            xml.ClearSymbols(); // no html symbols (to avoid convering spaces into  

            bok = xml.Write();
            if(!bok){
                POSITION pos = xml.m_ErrorList.GetHeadPosition();
                while(pos!=NULL){
                    printf(xml.m_ErrorList.GetAt(pos)+"\n");
                    xml.m_ErrorList.GetNext(pos);
                }
            }
        }