Writing an XmlPyxWriter

Using XmlTextWriter is very simple, if you want to write your XML in standard angle-brackets syntax. But since you learned how to read PYX in Chapter 3, you should learn how to write PYX here.

Because PYX does not handle many XML structural features, the implementation is quite simple. Example 4-6 shows a possible implementation of an XmlPyxWriter. After you look over the code, I’ll highlight some important bits.

Example 4-6. XmlPyxWriter implementation
using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Xml;

public class XmlPyxWriter : XmlWriter {

  // constructors

  public XmlPyxWriter(TextWriter writer) {
    this.writer = writer;
  }

  public XmlPyxWriter(Stream stream) {
    this.writer = new StreamWriter(stream);
  }

  public XmlPyxWriter(string filename) {
    this.writer = new StreamWriter(filename);
  }

  // private instance variables

  private TextWriter writer;
  private WriteState writeState = WriteState.Start;
  private XmlSpace xmlSpace = XmlSpace.Default;
  private string xmlLang = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName;
  private Stack elementNames = new Stack( );

  // private instance methods

  private void Write(string text) {
    writer.WriteLine("-{0}", text);
    if (writeState == WriteState.Element) {
      writeState = WriteState.Content;
    }
  }

  private void Write(char ch) {
    writer.WriteLine("-{0}", ch);
    if (writeState == WriteState.Element) {
      writeState = WriteState.Content;
    }
  }

  private void Write(char [ ] buffer, int index, int count) {
    writer.WriteLine("-{0}", buffer, index, count);
    if (writeState == WriteState.Element) {
      writeState = WriteState.Content;
    }
  }

  // properties from XmlWriter

  public override WriteState WriteState { 
    get { return writeState; }
  }

  public override XmlSpace XmlSpace { 
    get { return xmlSpace; } 
  }

  public override string XmlLang { 
    get { return xmlLang; } 
  }

  // methods from XmlWriter

  public override void WriteEndDocument( ) { 
    // no-op
  }

  public override void WriteComment(string text) { 
    // no-op
  }

  public override void WriteStartDocument( ) { 
    writeState = WriteState.Prolog;
  }

  public override void WriteStartDocument(bool standalone) { 
    writeState = WriteState.Prolog;
  }

  public override void WriteDocType(string name, string pubid, string sysid, string subset){ 
    writeState = WriteState.Prolog;
  }

  public override void WriteStartElement(string prefix, string localName, string ns) { 
    writer.WriteLine("({0} ", localName);
    elementNames.Push(localName);
    writeState = WriteState.Element;
  }

  public override void WriteEndElement( ) { 
    writer.WriteLine("){0}", elementNames.Pop( ));
  }

  public override void WriteFullEndElement( ) { 
    WriteEndElement( );
  }

  public override void WriteStartAttribute(string prefix, string localName, string ns) { 
    writer.Write("A{0} ",localName);
    writeState = WriteState.Attribute;
  }

  public override void WriteEndAttribute( ) { 
    writer.WriteLine( );
    writeState = WriteState.Element;
  }

  public override void WriteProcessingInstruction(string name, string text) { 
    writer.WriteLine("?{0} {1}", name, text);
  }

  public override void WriteEntityRef(string name) { 
    char ch = ' ';
    switch (name) {
      case "amp":
        ch = '&';
        break;
      case "lt":
        ch = '<';
        break;
      case "gt":
        ch = '>';
        break;
      case "quot":
        ch = '"';
        break;
      case "apos":
        ch = '\'';
        break;
    }
    Write(ch);
  }

  public override void WriteCData(string text) { 
    Write(text);
  }

  public override void WriteCharEntity(char ch) { 
    Write(ch);
  }

  public override void WriteWhitespace(string ws) { 
    Write(ws);
  }

  public override void WriteString(string text) {
    if (writeState == WriteState.Attribute) {
      writer.Write("{0}", text);
    } else {
      Write(text);
    }
  }

  public override void WriteSurrogateCharEntity(char lowChar, char highChar) {
    Write(lowChar);
    Write(highChar);
  }

  public override void WriteChars(char [ ] buffer, int index, int count) { 
    Write(buffer, index, count);
  }

  public override void WriteRaw(char [ ] buffer, int index, int count) { 
    Write(buffer, index, count);
  }

  public override void WriteRaw(string data) { 
    Write(data);
  }

  public override void WriteBase64(byte [ ] buffer, int index, int count) { 
    Write(writer.Encoding.GetChars(buffer), index, count);
  }

  public override void WriteBinHex(byte [ ] buffer, int index, int count) { 
    Write(writer.Encoding.GetChars(buffer), index, count);
  }

  public override void Close( ) { 
    writer.Close( );
    writeState = WriteState.Closed;
  }

  public override void Flush( ) { 
    writer.Flush( );
  }

  public override string LookupPrefix(string ns) { 
    return string.Empty;
  }

  public override void WriteNmToken(string name) { 
    writer.Write(name);
  }

  public override void WriteName(string name) { 
    writer.Write(name);
  }

  public override void WriteQualifiedName(string localName, string ns) { 
    writer.Write(localName);
  }
}

As you can see, it’s not a terribly complicated class. Most of the code is just a matter of filling in the abstract methods defined in XmlWriter. Here’s a run-down of what it does, beginning with the private instance variables:

private TextWriter writer;
private WriteState writeState = WriteState.Start;
private XmlSpace xmlSpace = XmlSpace.Default;
private string xmlLang = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName;
private Stack elementNames = new Stack( );

These five private instance variables maintain all the state information you’ll need in order to write PYX. writer is the TextWriter instance to which you are writing data. writeState is an instance of the XmlWriteState enumeration, indicating the state of the XmlWriter. xmlSpace is an instance of the XmlSpace enumeration, indicating the current xml:space scope. xmlLang is a string, indicating the current xml:lang scope. Finally, elementNames is a Stack of element names, which you’ll want to maintain so that you can write the appropriate element name when you close the element:

Tip

Since PYX has no concept of xml:space or xml:lang, xmlSpace is initialized to XmlSpace.Default and xmlLang is initialized to the current environment’s language.

private void Write(string text) {
  writer.WriteLine("-{0}", text);
  if (writeState == WriteState.Element) {
    writeState = WriteState.Content;
  }
}

private void Write(char ch) {
  writer.WriteLine("-{0}", ch);
  if (writeState == WriteState.Element) {
    writeState = WriteState.Content;
  }
}

private void Write(char [ ] buffer, int index, int count) {
  writer.WriteLine("-{0}", buffer, index, count);
  if (writeState == WriteState.Element) {
    writeState = WriteState.Content;
  }
}

These three private instance methods proxy the TextWriter.Write( ) and WriteLine( ) methods in such a way as to maintain the WriteState correctly. When the method is called, it writes a start text character followed by the text. If the writeState is currently WriteState.Element, it sets the state to WriteState.Content. Otherwise, it can be assumed that you’re either already writing content, or you’re writing an attribute’s value; you leave the state as it is and merely write the text:

public XmlPyxWriter(TextWriter writer) {
  this.writer = writer;
}

public XmlPyxWriter(Stream stream) {
  this.writer = new StreamWriter(stream);
}

public XmlPyxWriter(string filename) {
  this.writer = new StreamWriter(filename);
}

XmlPyxWriter has three constructors, one taking a TextWriter, one taking a Stream, and one taking a filename. Each of them initializes the writer instance variable in different ways, but the end result in each case is a TextWriter, ready to be written to.

The next section of code contains implementations of XmlWriter’s abstract properties and methods:

public override WriteState WriteState { 
  get { return writeState; }
}

public override XmlSpace XmlSpace { 
  get { return xmlSpace; } 
}

public override string XmlLang { 
  get { return xmlLang; } 
}

These three properties simply return the values of their respective private instance variables, without any further trickery:

public override void WriteEndDocument( ) { 
  // no-op
}

public override void WriteComment(string text) { 
  // no-op
}

These two method bodies are empty, because although PYX does not actually include the concept of comments or an end document, there’s no need to throw an exception if client code calls these methods. There is no data lost, because the start document is implied, and comments are not meaningful to an XML parser—especially not a PYX parser:

public override void WriteStartDocument( ) { 
  writeState = WriteState.Prolog;
}

public override void WriteStartDocument(bool standalone) { 
  writeState = WriteState.Prolog;
}

public override void WriteDocType(string name, string pubid, string sysid, string subset) { 
  writeState = WriteState.Prolog;
}

These three methods do not actually write anything to a PYX file; however, they do affect the state of the XmlWriter. Calling one of these methods indicates that the parser is currently in the document’s prolog. The XmlState.Prolog value is not used internally, but any of your client code that queries XmlState can use the information however you want to:

public override void WriteStartElement(string prefix, string localName, string ns) { 
  writer.WriteLine("({0} ", localName);
  elementNames.Push(localName);
  writeState = WriteState.Element;
}

WriteStartElement( ) , like all methods in XmlPyxWriter, ignores any namespace and prefix information. It writes the element to the TextWriter, pushes the element name onto the Stack, and sets the state to WriteState.Element:

public override void WriteEndElement( ) { 
  writer.WriteLine("){0}", elementNames.Pop( ));
}

public override void WriteFullEndElement( ) { 
  WriteEndElement( );
}

In PYX, both WriteEndElement( ) and WriteFullEndElement( ) do the same thing—and WriteFullEndElement( ) does it by simply calling WriteEndElement( ) directly. WriteEndElement( ) pops the current element name off the Stack and writes the PYX close element tag:

public override void WriteStartAttribute(string prefix, string localName, string ns) { 
  writer.Write("A{0} ",localName);
  writeState = WriteState.Attribute;
}

Similar to WriteStartElement( ), WriteStartAttribute( ) writes the PYX start attribute tag to the TextWriter, ignoring namespace information. However, it does not push the attribute’s name to the Stack; the Stack only contains element names:

public override void WriteEndAttribute( ) { 
  writer.WriteLine( );
  writeState = WriteState.Element;
}

WriteEndAttribute( ) finishes the attribute line and sets the writeState back to WriteState.Element to indicate that whatever comes next, whether it’s element content or a close element tag, applies to an element and not an attribute:

public override void WriteProcessingInstruction(string name, string text) { 
  writer.WriteLine("?{0} {1}", name, text);
}

WriteProcessingInstruction( ) writes a processing instruction, which is indicated by a line starting with a question mark, followed by the processing instruction name and its text:

public override void WriteEntityRef(string name) { 
  char ch = ' ';
  switch (name) {
    case "amp":
      ch = '&';
      break;
    case "lt":
      ch = '<';
      break;
    case "gt":
      ch = '>';
      break;
    case "quot":
      ch = '"';
      break;
    case "apos":
      ch = '\'';
      break;
  }
  Write(ch);
}

Tip

There is no WriteXmlDeclaration( ) method in XmlWriter, because the XML declaration is written by the WriteStartDocument( ) method.

Although PYX has no concept of entities and entity references, you can at least convert the known XML entities to their respective character representations. WriteEntityRef( ) does this, calling one of the private Write( ) methods to maintain the state and write a dash at the beginning of the line, if necessary:

public override void WriteCData(string text) { 
  Write(text);
}

public override void WriteCharEntity(char ch) { 
  Write(ch);
}

public override void WriteWhitespace(string ws) { 
  Write(ws);
}

public override void WriteString(string text) {
  if (writeState == WriteState.Attribute) {
    writer.Write("{0}", text);
  } else {
    Write(text);
  }
}

public override void WriteSurrogateCharEntity(char lowChar, char highChar) {

  Write(lowChar);
  Write(highChar);
}

public override void WriteChars(char [ ] buffer, int index, int count) { 
  Write(buffer, index, count);
}

public override void WriteRaw(char [ ] buffer, int index, int count) { 
  Write(buffer, index, count);
}

public override void WriteRaw(string data) { 
  Write(data);
}

public override void WriteBase64(byte [ ] buffer, int index, int count) { 
  Write(writer.Encoding.GetChars(buffer), index, count);
}

public override void WriteBinHex(byte [ ] buffer, int index, int count) { 
  Write(writer.Encoding.GetChars(buffer), index, count);
}

These methods, which write textual data in one form or another, all do more or less the same thing by delegating to the private Write( ) method. PYX has no concept of the different types of text these methods represent, so they are all treated the same way:

public override void Close( ) { 
  writer.Close( );
  writeState = WriteState.Closed;
}

public override void Flush( ) { 
  writer.Flush( );
}

These two methods simply delegate their work to the TextWriter. Additionally, Close( ) sets the writeState to WriteState.Closed:

public override string LookupPrefix(string ns) { 
  return string.Empty;
}

LookupPrefix( ) returns an empty string because, again, PYX has no concept of namespaces:

public override void WriteNmToken(string name) { 
  writer.Write(name);
}

public override void WriteName(string name) { 
  writer.Write(name);
}

public override void WriteQualifiedName(string localName, string ns) { 
  writer.Write(localName);
}

The previous three methods are intended to ensure that names written to XML files are valid according to the XML specification. PYX has no concept of valid names, so these methods just write the names to the Stream as is.

Now that XmlPyxWriter is complete, you can take the XmlWriter client code from Example 3-3 and make one small change to start writing the output in PYX:

public static void Main(string [ ] args) {

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

  // Set the formatting to something nice - not supported by XmlPyxWRiter
  //writer.Formatting = Formatting.Indented;

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

  ...

You’ll notice that I’ve deleted the line that set the XmlFormatting, as that is a property of XmlTextWriter, and has no meaning in the context of a PYX writer.

Here’s the resulting PYX, representing the same data as the XML version:

(root
Aid _1
Aname bar
(element1
-some characters
)element1
(cdataElement
Adate 7/4/2002 1:38:42 PM
-<this contains some characters XML wouldn't like & would choke on
-<this contains some characters XML wouldn't like & so the XmlWriter replaces them
)cdataElement
(emptyElement
)emptyElement
(emptyElement
)emptyElement
-One string
-&
- another.
)root

Get .NET & XML now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.