Published: 2005-11-22
This is part 9/14 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[] =
{
{ '"', """ },
{ '&', "&" },
};
static const CharTranslation CharDataTranslations[] =
{
{ '&', "&" },
{ '<', "<" },
{ '>', ">" },
};
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;
}
}