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