DekGenius.com
[ Team LiB ] Previous Section Next Section

3.2 XmlWriter and Its Subclasses

XmlWriter is an abstract base class that defines the interface for creating XML output programmatically. It contains methods such as WriteStartElement( ) and WriteEndElement( ) to write data. XmlWriter maintains the state of the XML document as it writes, so it knows which start element or attribute to close when you call WriteEndElement( ) or WriteEndAttribute( ).

XmlTextWriter is the subclass of XmlWriter, which provides support for output of XML to any Stream, filename, or TextWriter. In addition to all the required features of an XmlWriter, XmlTextWriter allows you to set the formatting of the output, using the Formatting, Indentation, IndentChar, Namespaces, and QuoteChar properties.

The XmlTextWriter formatting properties are described in Table 3-6.

Table 3-6. XmlTextWriter formatting properties

Property

Type

Description

Formatting
System.Xml.Formatting

Specify Formatting.None if the XML is to be produced without indentation, or Formatting.Indented to produce indented XML. Formatting.Indented makes for more readable output, but the canonical XML produced is identical.

Indentation
int

If Formatting is set to Formatting.Indented, Indentation specifies the number of characters by which to indent each successive level of markup.

IndentChar
char

If Formatting is set to Formatting.Indented, IndentChar specifies the character with which to indent each successive level of markup. To ensure valid XML, you should use any valid XML whitespace character.

Namespaces
bool

Specifies whether the XmlTextWriter supports W3C XML Namespaces.

QuoteChar
char

Specifies the character with which to quote attribute values. QuoteChar may be either a single quote or a double quote. Setting QuoteChar to any other character will cause an ArgumentException to be thrown.

With an XmlTextWriter, you always have access to the base Stream through the BaseStream property. This is useful for manipulating the Stream at runtime, such as calling Seek( ) to reset the Stream's position.

3.2.1 When to Use XmlWriter

You should use XmlWriter when you want to output data from your program to a file or Stream in XML format. Since XmlTextWriter's constructors take a Stream, a filename, and a TextWriter, respectively, you can output to any common I/O target.

In addition, any .NET class that takes an XmlWriter to output its data as XML may use any XmlWriter subclass. This means, for example, that you may output a DOM tree in any of the supported formats. You may also send data to any other format, as long as an XmlWriter exists to produce that format.

Like XmlReader, you can extend XmlWriter to produce output in alternative XML syntaxes—or even syntaxes that have nothing whatsoever to do with XML, as long as the XmlWriter interface is supported.

3.2.2 Using the XmlWriter

XmlWriter contains methods to write any type of XML node to its output Stream. There is just one subclass in the .NET Framework, XmlTextWriter, which will handle most of your XML output needs.

Example 3-3 shows a short program that writes data to an XmlTextWriter.

Example 3-3. Program to write XML with XmlTextWriter
using System;
using System.IO;
using System.Text;
using System.Xml;

public class WriteXml {
  public static void Main(string [ ] args) {

    // Create the XmlWriter
    XmlTextWriter writer = new XmlTextWriter(Console.Out);

    // Set the formatting to something nice
    writer.Formatting = Formatting.Indented;

    // Write the XML declaration
    writer.WriteStartDocument(true);

    // Write a comment
    writer.WriteComment("the first element follows.");

    // Start the root element
    writer.WriteStartElement("root");

    // Write an attribute
    writer.WriteAttributeString("id","_1");
  
    // Write another attribute
    writer.WriteStartAttribute"name", "foo");
    writer.WriteString("bar");
    writer.WriteEndAttribute( );

    // Write another element
    writer.WriteElementString("element1","some characters");

    writer.WriteStartElement("cdataElement");
    writer.WriteAttributeString("date",DateTime.Now.ToString( ));
    writer.WriteCData("<this contains some characters XML wouldn't like & would choke on");
    writer.WriteString("<this contains some characters XML wouldn't like & so the XmlWriter replaces them");
    writer.WriteEndElement( );

    // Write an empty element
    writer.WriteStartElement("emptyElement");
    writer.WriteEndElement( );

    // Write another empty element
    writer.WriteStartElement("emptyElement","foo");
    writer.WriteFullEndElement( );

    // Write some text
    writer.WriteString("One string ");
    writer.WriteEntityRef("amp");
    writer.WriteString(" another.");

    // Close the root element
    writer.WriteEndElement( );
  
    // End the document
    writer.WriteEndDocument( );

    // Flush and close the output stream
    writer.Flush( );
    writer.Close( );
  }
}

Let's walk through this code in small chunks:

XmlTextWriter writer = new XmlTextWriter(Console.Out);

Console.Out is just like any other TextWriter, so you can pass it to the XmlTextWriter constructor. If you wanted, you could have instead created a new StreamWriter and written the XML to a file or an HttpWebRequest:

writer.Formatting = Formatting.Indented;

This produces XML formatted with indenting, as described previously. It's easier to read, but doesn't affect the code at all:

writer.WriteStartDocument(true);

This line writes the XML declaration. There are two overloads of WriteStartDocument( ); Example 3-3 uses the one that takes a bool parameter, indicating that the XML declaration should include standalone="yes". XmlWriter will determine the correct encoding from the underlying TextWriter. If I had chosen to write to a file, the encoding would be UTF8:

My computer indicated that the encoding when writing to the console was IBM437, but since this is generated automatically by the operating system and .NET implementation, your mileage may vary.


writer.WriteComment("the first element follows.");

This line writes an XML comment to the Stream. Although XML comments are enclosed in angle brackets (<!-- ... -->), all you have to pass to WriteComment( ) is the text of the comment:

writer.WriteStartElement("root");

Now I begin to write the first element. The WriteStartElement( ) method writes the opening angle bracket and the element name (<root) to the Stream; XmlWriter is smart enough to wait for any attributes before writing the closing angle bracket. There are several overloads of WriteStartElement( ), providing the flexibility to specify a namespace and prefix. Here I'm just writing a plain element name:

writer.WriteAttributeString("id","_1");

Before moving on to the next element, this line adds an attribute to the root element. Like WriteStartElement( ), WriteAttributeString( ) has several overloads, allowing you to specify the namespace and prefix, as well as the local name and value:

writer.WriteStartAttribute("name", "foo");
writer.WriteString("bar");
writer.WriteEndAttribute( );

These lines use another method, WriteStartAttribute( ), that has similar overloads. In this case, I have indicated the start of an attribute named name, whose namespace is foo. Because I did not specify a prefix, XmlWriter will automatically assign a prefix for the namespace. If I had previously used the same namespace in a different element or attribute, XmlWriter is smart enough to use the same prefix, whether I had assigned the prefix myself or had one assigned for me by XmlWriter.

After starting to write the attribute, I've used WriteString( ) to write the attribute's value, "bar", and finally closed the attribute with WriteEndAttribute( ):

writer.WriteElementString("element1","some characters");

WriteElementString( ), like WriteAttributeString( ), writes the entire element with the specified text content. It also includes the closing angle bracket, so there is no need to call WriteEndElement( ):

writer.WriteStartElement("cdataElement");
writer.WriteAttributeString("date",DateTime.Now.ToString( ));
writer.WriteCData("<this contains some characters XML wouldn't like & would choke on");
writer.WriteString("<this contains some characters XML wouldn't like & so the XmlWriter replaces them");
writer.WriteEndElement( );

This step writes an element named cdataElement. This element has an attribute called date, but, more importantly, its content begins with a CDATA section. XmlWriter takes care of adding the <![CDATA[...]]> markup around the character data. The call to WriteString( ) writes a similar string; in this case, XmlWriter will replace the special characters with the appropriate entities:

writer.WriteStartElement("emptyElement");
writer.WriteEndElement( );

writer.WriteStartElement("emptyElement","foo");
writer.WriteFullEndElement( );

These four lines show two different ways to write empty elements. The first one will be written as <emptyElement/> because XmlWriter knows that the element is empty. In the second one, even though the element is empty, XmlWriter will write a full end element (<emptyElement></emptyElement>) because WriteFullEndElement( ) was specifically called.

In addition, in the second empty element, I specified the namespace foo. Because it is the same namespace used in an attribute earlier, XmlWriter automatically uses the same prefix, which it originally assigned when writing that attribute:

writer.WriteString("One string ");
writer.WriteEntityRef("amp");
writer.WriteString(" another.");

These lines write some text to the XML. WriteString( ) writes the text verbatim, while WriteEntityRef( ) writes an entity reference. WriteEntityRef( ) takes care of formatting the entity reference correctly; in this case &amp; will be written to the Stream:

writer.WriteEndElement( );
writer.WriteEndDocument( );

The final call to WriteEndElement( ) closes the root element, because XmlWriter keeps track of its depth in the node tree. WriteEndDocument( ) closes the document:

writer.Flush( );
writer.Close( );

Finally, you should always remember to flush and close the Stream.

Compiling and running this program, the following output is written to the console:

<?xml version="1.0" encoding="IBM437" standalone="yes"?>
<!--the first element follows.-->        
<root id="_1" d1p1:name="bar" xmlns:d1p1="foo">
  <element1>some characters</element1>
  <cdataElement date="7/1/2002 9:26:22 PM"><![CDATA[<this contains some 
characters XML wouldn't like & would choke on]]>&lt;this contains some 
characters XML wouldn't like &amp; so the XmlWriter replaces them</cdataElement>
  <emptyElement />
  <d1p1:emptyElement>
</d1p1:emptyElement>One string &amp; another.</root>

Remember the root element, with its name="bar" attribute? In the output, you can see that XmlWriter selected the prefix d1p1 for the namespace foo. It also remembered this prefix and used it for the emptyElement element later in the output. Remember that I could have assigned the prefix myself, with a different overload of WriteStartAttribute( ):

writer.WriteStartAttribute("baz", "name", "foo");

The output would then have changed to this:

<?xml version="1.0" encoding="IBM437" standalone="yes"?>
<!--the first element follows.-->        
<root id="_1" baz:name="bar" xmlns:baz="foo">
  <element1>some characters</element1>
  <cdataElement date="7/1/2002 9:26:22 PM"><![CDATA[<this contains some 
characters XML wouldn't like & would choke on]]>&lt;this contains some 
characters XML wouldn't like &amp; so the XmlWriter replaces them</cdataElement>
  <emptyElement />
  <baz:emptyElement>
</baz:emptyElement>One string &amp; another.</root>

Notice that subsequent calls to WriteStartElement( ) with the same name still properly pick up the prefix, whether you set it or XmlWriter sets it.

    [ Team LiB ] Previous Section Next Section