Note that the tag is closed. The .Net XML writer keeps that open until you try and go into a conflicting state such as a new start tag, comment, PI, or such. Calling Flush() simply doesn’t work.
My previous methodology used a comment to close it. Thus something like this was sent out:
Not the best. Some clients dont like comments and the XMPP specification says that comments SHOULD NOT be used (note, not MUST NOT), so some parsers fall over.
I finally decided to give fixing it a go. I created a root XML writer class that had one abstract method: CompleteElement(). I won’t paste that class in because it is trivial. I cracked open Reflector and figured out if there was a common method in XmlTextWriter that handles this. I was in luck, there was (AutoComplete)! My first attempt simply reflected over the XmlTextWriter and found the method, enum and enum field. It didn’t work. XmlWriter.Create() hands out XmlWellFormedWriters (you can’t instantiate these directly, the class is internal). So I looked at it using Reflector and I was in luck again! The only thing that is different is the name of the method (AdvanceState) and everything else was exactly the same.
Here it is (most of it is purely wrappers):
Complete Streaming XML Writer
/// <summary>
/// Represents a streaming xml text writer.
/// </summary>
public class StreamingXmlTextWriter : StreamingXmlWriter
{
private static object __autoCompleteComment;
private static MethodInfo __autoCompleteMethod;
static StreamingXmlTextWriter()
{
// Get the type.
Type wellFormedWriter = typeof(XmlTextWriter).Assembly.GetType("System.Xml.XmlWellFormedWriter");
// Find the method.
__autoCompleteMethod = wellFormedWriter.GetMethod("AdvanceState", BindingFlags.Instance | BindingFlags.NonPublic);
// Find the argument.
Type tokenEnum = wellFormedWriter.GetNestedType("Token", BindingFlags.NonPublic);
FieldInfo tokenField = tokenEnum.GetField("Comment", BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
__autoCompleteComment = tokenField.GetValue(null);
}
private XmlWriter writer;
public StreamingXmlTextWriter(Stream stream, Encoding encoding)
: base(stream, encoding)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = encoding;
settings.OmitXmlDeclaration = true;
writer = XmlWriter.Create(stream, settings);
}
#region Wrapped Methods
public override void Close()
{
writer.Close();
}
public override void Flush()
{
writer.Flush();
}
public override string LookupPrefix(string ns)
{
return writer.LookupPrefix(ns);
}
public override void WriteBase64(byte[] buffer, int index, int count)
{
if (skippedAttribute)
return;
writer.WriteBase64(buffer, index, count);
}
public override void WriteCData(string text)
{
if (skippedAttribute)
return;
writer.WriteCData(text);
}
public override void WriteCharEntity(char ch)
{
if (skippedAttribute)
return;
writer.WriteCharEntity(ch);
}
public override void WriteChars(char[] buffer, int index, int count)
{
if (skippedAttribute)
return;
writer.WriteChars(buffer, index, count);
}
public override void WriteComment(string text)
{
writer.WriteComment(text);
}
public override void WriteDocType(string name, string pubid, string sysid, string subset)
{
writer.WriteDocType(name, pubid, sysid, subset);
}
public override void WriteEndAttribute()
{
if (skippedAttribute)
{
skippedAttribute = false;
return;
}
writer.WriteEndAttribute();
}
public override void WriteEndDocument()
{
writer.WriteEndDocument();
}
public override void WriteEndElement()
{
writer.WriteEndElement();
}
public override void WriteEntityRef(string name)
{
if (skippedAttribute)
return;
writer.WriteEntityRef(name);
}
public override void WriteFullEndElement()
{
writer.WriteFullEndElement();
}
public override void WriteProcessingInstruction(string name, string text)
{
writer.WriteProcessingInstruction(name, text);
}
public override void WriteRaw(string data)
{
writer.WriteRaw(data);
}
public override void WriteRaw(char[] buffer, int index, int count)
{
writer.WriteRaw(buffer, index, count);
}
private bool skippedAttribute;
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
// XSI/XSD must not be emitted.
if (prefix == "xmlns" || localName == "xmlns")
{
if (localName == "xsi" || localName == "xsd")
{
skippedAttribute = true;
return;
}
ApplyNamespace(prefix, localName, ref ns);
}
writer.WriteStartAttribute(prefix, localName, ns);
}
public override void WriteStartDocument(bool standalone)
{
writer.WriteStartDocument(standalone);
}
public override void WriteStartDocument()
{
writer.WriteStartDocument();
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
writer.WriteStartElement(prefix, localName, ns);
}
public override System.Xml.WriteState WriteState
{
get { return writer.WriteState; }
}
public override void WriteString(string text)
{
if (skippedAttribute)
return;
writer.WriteString(text);
}
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
if (skippedAttribute)
return;
writer.WriteSurrogateCharEntity(lowChar, highChar);
}
public override void WriteWhitespace(string ws)
{
if (skippedAttribute)
return;
writer.WriteWhitespace(ws);
}
public override XmlWriterSettings Settings
{
get
{
return writer.Settings;
}
}
public override string XmlLang
{
get
{
return writer.XmlLang;
}
}
public override XmlSpace XmlSpace
{
get
{
return writer.XmlSpace;
}
}
#endregion
public override void CompleteElement()
{
PerformAutoComplete();
}
private void PerformAutoComplete()
{
__autoCompleteMethod.Invoke(writer, new object[] { __autoCompleteComment });
}
}
Jonathan Dickinson works at SourceCode. Everything posted on this blog is his personal opinion and do not necessarily represent the views of his employer or his employer's clients.
How to close a XML Element using XmlTextWriter
Recently with my forays into the XMPP land, I have needed to handle the case of writing out the following XML (as is, without the closing tag):
Note that the tag is closed. The .Net XML writer keeps that open until you try and go into a conflicting state such as a new start tag, comment, PI, or such. Calling Flush() simply doesn’t work.
My previous methodology used a comment to close it. Thus something like this was sent out:
Not the best. Some clients dont like comments and the XMPP specification says that comments SHOULD NOT be used (note, not MUST NOT), so some parsers fall over.
I finally decided to give fixing it a go. I created a root XML writer class that had one abstract method: CompleteElement(). I won’t paste that class in because it is trivial. I cracked open Reflector and figured out if there was a common method in XmlTextWriter that handles this. I was in luck, there was (AutoComplete)! My first attempt simply reflected over the XmlTextWriter and found the method, enum and enum field. It didn’t work. XmlWriter.Create() hands out XmlWellFormedWriters (you can’t instantiate these directly, the class is internal). So I looked at it using Reflector and I was in luck again! The only thing that is different is the name of the method (AdvanceState) and everything else was exactly the same.
Here it is (most of it is purely wrappers):
/// <summary> /// Represents a streaming xml text writer. /// </summary> public class StreamingXmlTextWriter : StreamingXmlWriter { private static object __autoCompleteComment; private static MethodInfo __autoCompleteMethod; static StreamingXmlTextWriter() { // Get the type. Type wellFormedWriter = typeof(XmlTextWriter).Assembly.GetType("System.Xml.XmlWellFormedWriter"); // Find the method. __autoCompleteMethod = wellFormedWriter.GetMethod("AdvanceState", BindingFlags.Instance | BindingFlags.NonPublic); // Find the argument. Type tokenEnum = wellFormedWriter.GetNestedType("Token", BindingFlags.NonPublic); FieldInfo tokenField = tokenEnum.GetField("Comment", BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); __autoCompleteComment = tokenField.GetValue(null); } private XmlWriter writer; public StreamingXmlTextWriter(Stream stream, Encoding encoding) : base(stream, encoding) { XmlWriterSettings settings = new XmlWriterSettings(); settings.Encoding = encoding; settings.OmitXmlDeclaration = true; writer = XmlWriter.Create(stream, settings); } #region Wrapped Methods public override void Close() { writer.Close(); } public override void Flush() { writer.Flush(); } public override string LookupPrefix(string ns) { return writer.LookupPrefix(ns); } public override void WriteBase64(byte[] buffer, int index, int count) { if (skippedAttribute) return; writer.WriteBase64(buffer, index, count); } public override void WriteCData(string text) { if (skippedAttribute) return; writer.WriteCData(text); } public override void WriteCharEntity(char ch) { if (skippedAttribute) return; writer.WriteCharEntity(ch); } public override void WriteChars(char[] buffer, int index, int count) { if (skippedAttribute) return; writer.WriteChars(buffer, index, count); } public override void WriteComment(string text) { writer.WriteComment(text); } public override void WriteDocType(string name, string pubid, string sysid, string subset) { writer.WriteDocType(name, pubid, sysid, subset); } public override void WriteEndAttribute() { if (skippedAttribute) { skippedAttribute = false; return; } writer.WriteEndAttribute(); } public override void WriteEndDocument() { writer.WriteEndDocument(); } public override void WriteEndElement() { writer.WriteEndElement(); } public override void WriteEntityRef(string name) { if (skippedAttribute) return; writer.WriteEntityRef(name); } public override void WriteFullEndElement() { writer.WriteFullEndElement(); } public override void WriteProcessingInstruction(string name, string text) { writer.WriteProcessingInstruction(name, text); } public override void WriteRaw(string data) { writer.WriteRaw(data); } public override void WriteRaw(char[] buffer, int index, int count) { writer.WriteRaw(buffer, index, count); } private bool skippedAttribute; public override void WriteStartAttribute(string prefix, string localName, string ns) { // XSI/XSD must not be emitted. if (prefix == "xmlns" || localName == "xmlns") { if (localName == "xsi" || localName == "xsd") { skippedAttribute = true; return; } ApplyNamespace(prefix, localName, ref ns); } writer.WriteStartAttribute(prefix, localName, ns); } public override void WriteStartDocument(bool standalone) { writer.WriteStartDocument(standalone); } public override void WriteStartDocument() { writer.WriteStartDocument(); } public override void WriteStartElement(string prefix, string localName, string ns) { writer.WriteStartElement(prefix, localName, ns); } public override System.Xml.WriteState WriteState { get { return writer.WriteState; } } public override void WriteString(string text) { if (skippedAttribute) return; writer.WriteString(text); } public override void WriteSurrogateCharEntity(char lowChar, char highChar) { if (skippedAttribute) return; writer.WriteSurrogateCharEntity(lowChar, highChar); } public override void WriteWhitespace(string ws) { if (skippedAttribute) return; writer.WriteWhitespace(ws); } public override XmlWriterSettings Settings { get { return writer.Settings; } } public override string XmlLang { get { return writer.XmlLang; } } public override XmlSpace XmlSpace { get { return writer.XmlSpace; } } #endregion public override void CompleteElement() { PerformAutoComplete(); } private void PerformAutoComplete() { __autoCompleteMethod.Invoke(writer, new object[] { __autoCompleteComment }); } }