Implementing IXmlWriter Part 14: Supporting Writing To A Stream


Implementing IXmlWriter Part 14: Supporting Writing To A Stream

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:

  1. OstreamXmlWriter is passed a copy (or const reference) of the ostream instance in its constructor and stores a copy of the ostream instance 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.
  2. OstreamXmlWriter is passed a reference (pointer) to the ostream instance in its constructor and stores a reference (pointer) to the ostream instance as a member variable. Advantages: Relatively simple. Disadvantages: OstreamXmlWriter becomes implicitly tied to the lifetime of the passed object, which means that a user must be sure that OstreamXmlWriter is destroyed before the ostream instance is. Code such as stringstream* ss = new stringstream(); XmlWriter w(ss); delete ss; w.WriteStartElement("blah"); may result in hard-to-find bugs.
  3. OstreamXmlWriter is passed a smart pointer object (e.g. std::auto_ptr, boost::shared_ptr) which points to the ostream instance in its constructor and stores a copy of the smart pointer object as a member variable. Advantages: Lifetime issues are handled correctly. Disadvantages: The ostream instance 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-constructed ostream* in the constructor, store as a smart pointer member variable. However, what if a stack-constructed ostream is passed?)
  4. OstreamXmlWriter takes a reference to the ostream instance as an extra parameter for every XML writing function. Advantages: Parallels operator<<, no lifetime issues. Disadvantages: Breaks IXmlWriter-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.WriteElementString("child", "value");
    xmlWriter.WriteAttributeString("att", "value");

// 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.