This is part 14/14 of my Implementing IXmlWriter post series.
Today I will add support for writing the generated XML to a C++ stream to last time’s IXmlWriter.
Finally the reason why I’ve insisted on calling this series IXmlWriter (instead of StringXmlWriter) should become clear: I’ve been planning on supporting writing the generated XML to more than just a string. Specifically, today I will add the ability to write the XML to a C++ ostream object, a base class in the C++ iostream library which defines a writable stream.
To this end, I decided to write a pure-virtual interface called IXmlWriter which contains all the relevant methods for generating XML (e.g. WriteStartElement(), WriteComment(), etc.), and two concrete classes which implement this interface: StringXmlWriter (which writes to a string as before) and OstreamXmlWriter (which writes to a user-provided ostream instance). I moved the XML-generating logic from StringXmlWriter to OstreamXmlWriter and implemented StringXmlWriter in terms of OstreamXmlWriter and a stringstream instance.
The area where I probably spent the most time was deciding how to pass the ostream instance to OstreamXmlWriter and how OstreamXmlWriter would store it, if necessary. I came up with the following options:
OstreamXmlWriteris passed a copy (or const reference) of theostreaminstance in its constructor and stores a copy of theostreaminstance as a member variable. Advantages: The typical idiom for most variables passed in the constructor. Disadvantages: For correctness, this requires a semantically correct copy constructor which is very difficult, and often impossible, to write for streams.OstreamXmlWriteris passed a reference (pointer) to theostreaminstance in its constructor and stores a reference (pointer) to theostreaminstance as a member variable. Advantages: Relatively simple. Disadvantages:OstreamXmlWriterbecomes implicitly tied to the lifetime of the passed object, which means that a user must be sure thatOstreamXmlWriteris destroyed before theostreaminstance is. Code such asstringstream* ss = new stringstream(); XmlWriter w(ss); delete ss; w.WriteStartElement("blah");may result in hard-to-find bugs.OstreamXmlWriteris passed a smart pointer object (e.g.std::auto_ptr,boost::shared_ptr) which points to theostreaminstance in its constructor and stores a copy of the smart pointer object as a member variable. Advantages: Lifetime issues are handled correctly. Disadvantages: Theostreaminstance must be constructed on the heap and never the stack. We expose a dependency on a smart pointer implementation that users probably shouldn’t care about. (Alternative: Take a heap-constructedostream*in the constructor, store as a smart pointer member variable. However, what if a stack-constructedostreamis passed?)OstreamXmlWritertakes a reference to theostreaminstance as an extra parameter for every XML writing function. Advantages: Parallelsoperator<<, no lifetime issues. Disadvantages: BreaksIXmlWriter-as-interface.
Based on these observations, I decided to go with (2). Here’s the new test case:
std::stringstream ss;
OstreamXmlWriter xmlWriter(ss, StringXmlWriter::Formatting_Indented);
xmlWriter.WriteComment("comment");
xmlWriter.WriteStartElement("root");
xmlWriter.WriteElementString("child", "value");
xmlWriter.WriteComment("comment");
xmlWriter.WriteStartElement("child");
xmlWriter.WriteAttributeString("att", "value");
xmlWriter.WriteEndElement();
xmlWriter.WriteStartElement("child");
xmlWriter.WriteStartElement("child");
xmlWriter.WriteStartElement("child");
xmlWriter.WriteEndDocument();
// ss.str() should equal (whitespace is important):
// <!--comment-->
// <root>
// <child>value</child>
// <!--comment-->
// <child att="value"/>
// <child>
// <child>
// <child/>
// </child>
// </child>
// </root>
Because the source code is starting to get unwieldy (chorus: Too late!), I have linked to the source code files rather than insert the source code in-line with the post. Here’s the latest source code:
Unfortunately, the source code for this post has been lost.