Implementing IXmlWriter Part 9: Supporting WriteStartDocument() and WriteEndDocument()

Implementing IXmlWriter Part 9: Supporting WriteStartDocument() and WriteEndDocument()

This is part 9 of my Implementing IXmlWriter post series.

Today I will add support for the functions WriteStartDocument() and WriteEndDocument() to last time’s IXmlWriter.

WriteStartDocument() writes the XML declaration (i.e. <?xml version="1.0"?>) and WriteEndDocument() closes all open attributes and elements and sets the IXmlWriter back in the initial state. Adding support for these functions is straightforward. Note that I have introduced a new IXmlWriter state called WriteState_Prolog; this will be important later.

Here’s the test case:

StringXmlWriter xmlWriter;
xmlWriter.WriteStartDocument();
  xmlWriter.WriteStartElement("root");
    xmlWriter.WriteStartElement("child");
      xmlWriter.WriteStartAttribute("att");
        xmlWriter.WriteString("value");
xmlWriter.WriteEndDocument();

std::string strXML = xmlWriter.GetXmlString();
// strXML should be <?xml version="1.0"?><root><child att="value"/></root>

Here’s the new header file:

// StringXmlWriter.h

class StringXmlWriter
{
private:
    enum WriteState
    {
        WriteState_Attribute, // An attribute value is being written
        WriteState_Content, // Element content is being written
        WriteState_Element, // An element start tag has been written (and is unclosed)
        WriteState_Prolog, // The prolog is being written
        WriteState_Start, // No Write() methods have been called
    };

    WriteState m_writeState;
    std::stack<std::string> m_openedElements;
    std::string m_xmlStr;

public:
    StringXmlWriter();

    std::string GetXmlString() const;
    void WriteAttributeString(const std::string& localName,
                              const std::string& text);
    void WriteElementString(const std::string& localName,
                            const std::string& text);
    void WriteEndAttribute();
    void WriteEndDocument();
    void WriteEndElement();
    void WriteStartAttribute(const std::string& localName);
    void WriteStartDocument();
    void WriteStartElement(const std::string& localName);
    void WriteString(const std::string& text);

private:
    // Disable copy construction and assignment
    StringXmlWriter(const StringXmlWriter&);
    StringXmlWriter& operator=(const StringXmlWriter&);
};

Here’s the new implementation file:

// StringXmlWriter.cpp

#include "StringXmlWriter.h"

#define ARRAYSIZE(x) ( sizeof(x) / sizeof(x[0]) )

struct CharTranslation
{
    char OriginalChar;
    const char* ReplacementString;
};

static const CharTranslation AttributeValueTranslations[] =
{
    { '"', "&quot;" },
    { '&', "&amp;" },
};

static const CharTranslation CharDataTranslations[] =
{
    { '&', "&amp;" },
    { '<', "&lt;" },
    { '>', "&gt;" },
};

struct OriginalCharEquals :
    public std::binary_function<CharTranslation, char, bool>
{
    bool operator() (const CharTranslation& translation, char ch) const
    {
        return (translation.OriginalChar == ch);
    }
};

static std::string TranslateString(const std::string& originalStr,
                                   const CharTranslation* translations,
                                   int numTranslations)
{
    // Actually one past end, needed for proper std::find_if semantics
    const CharTranslation* endTranslations = translations + numTranslations;

    std::string translatedStr;
    for (std::string::const_iterator stringIter = originalStr.begin();
         stringIter != originalStr.end();
         ++stringIter)
    {
        char ch = *stringIter;
        const CharTranslation* translation = std::find_if
            (
            translations,
            endTranslations,
            std::bind2nd(OriginalCharEquals(), ch)
            );
        if (translation != endTranslations)
        {
            translatedStr += translation->ReplacementString;
        }
        else
        {
            translatedStr += ch;
        }
    }

    return translatedStr;
}

StringXmlWriter::StringXmlWriter() : m_writeState(WriteState_Start)
{
}

std::string StringXmlWriter::GetXmlString() const
{
    return m_xmlStr;
}

void StringXmlWriter::WriteAttributeString(const std::string& localName,
                                           const std::string& text)
{
    WriteStartAttribute(localName);
    WriteString(text);
    WriteEndAttribute();
}

void StringXmlWriter::WriteElementString(const std::string& localName,
                                         const std::string& text)
{
    WriteStartElement(localName);
    WriteString(text);
    WriteEndElement();
}

void StringXmlWriter::WriteEndAttribute()
{
    switch (m_writeState)
    {
    case WriteState_Attribute:
        m_xmlStr += '"';
        m_writeState = WriteState_Element;
        break;
    default:
        // TODO: Generate error
        break;
    }
}

void StringXmlWriter::WriteEndDocument()
{
    switch (m_writeState)
    {
    case WriteState_Attribute:
        WriteEndAttribute();
        // FALL THROUGH
    case WriteState_Element:
        while (!m_openedElements.empty())
        {
            WriteEndElement();
        }
        break;
    default:
        // TODO: Generate error
        break;
    }

    m_writeState = WriteState_Start;
}

void StringXmlWriter::WriteEndElement()
{
    switch (m_writeState)
    {
    case WriteState_Content:
        {
            m_xmlStr += "</";
            m_xmlStr += m_openedElements.top();
            m_xmlStr += '>';
            m_openedElements.pop();
            m_writeState = WriteState_Content;
            break;
        }
    case WriteState_Element:
        {
            m_xmlStr += "/>";
            m_openedElements.pop();
            m_writeState = WriteState_Content;
            break;
        }
    default:
        // TODO: Generate error
        break;
    }
}

void StringXmlWriter::WriteStartAttribute(const std::string& localName)
{
    switch (m_writeState)
    {
    case WriteState_Element:
        m_xmlStr += ' ';
        m_xmlStr += localName;
        m_xmlStr += "=\"";
        m_writeState = WriteState_Attribute;
        break;
    default:
        // TODO: Generate error
        break;
    }
}

void StringXmlWriter::WriteStartDocument()
{
    switch (m_writeState)
    {
    case WriteState_Start:
        m_xmlStr += "<?xml version=\"1.0\"?>";
        m_writeState = WriteState_Prolog;
        break;
    default:
        // TODO: Generate error
        break;
    }
}

void StringXmlWriter::WriteStartElement(const std::string& localName)
{
    switch (m_writeState)
    {
    case WriteState_Element:
        // An element is currently open. Close the element so we can open
        // a new one.
        m_xmlStr += '>';
        // FALL THROUGH
    case WriteState_Content:
    case WriteState_Prolog:
    case WriteState_Start:
        m_openedElements.push(localName);
        m_xmlStr += '<';
        m_xmlStr += localName;
        m_writeState = WriteState_Element;
        break;
    default:
        // TODO: Generate error
        break;
    }
}

void StringXmlWriter::WriteString(const std::string& text)
{
    switch (m_writeState)
    {
    case WriteState_Attribute:
        m_xmlStr += TranslateString
            (
            text,
            AttributeValueTranslations,
            ARRAYSIZE(AttributeValueTranslations)
            );
        break;
    case WriteState_Element:
        // An element is currently open. Close the element so we can start
        // writing the element content.
        m_xmlStr += '>';
        m_writeState = WriteState_Content;
        // FALL THROUGH
    case WriteState_Content:
        m_xmlStr += TranslateString
            (
            text,
            CharDataTranslations,
            ARRAYSIZE(CharDataTranslations)
            );
        break;
    default:
        // TODO: Generate error
        break;
    }
}