using System.Collections.Generic;
using System.Xml.Serialization;
public class NoEndBracketEscapingXmlWriter : XmlWriterDecorator
bool OnlyForAttributes { get; }
public NoEndBracketEscapingXmlWriter(XmlWriter baseWriter) : this(baseWriter, false) { }
public NoEndBracketEscapingXmlWriter(XmlWriter baseWriter, bool onlyForAttributes) : base(baseWriter) => this.OnlyForAttributes = onlyForAttributes;
public override void WriteString(string text)
if (WriteState == WriteState.Prolog || (WriteState != WriteState.Attribute && OnlyForAttributes))
int prevIndex = 0, index;
while ((index = text.IndexOf('>', prevIndex)) >= 0)
buffer = text.ToCharArray();
if (WriteState != WriteState.Attribute && text.AsSpan().Slice(prevIndex, index - prevIndex).EndsWith("]]"))
base.WriteChars(buffer, prevIndex, index - prevIndex + 1);
base.WriteChars(buffer, prevIndex, index - prevIndex);
else if (prevIndex < buffer.Length)
base.WriteChars(buffer, prevIndex, buffer.Length - prevIndex);
public class XmlWriterDecorator : XmlWriter
readonly XmlWriter baseWriter;
public XmlWriterDecorator(XmlWriter baseWriter) => this.baseWriter = baseWriter ?? throw new ArgumentNullException();
protected virtual bool IsSuspended { get { return false; } }
public override WriteState WriteState => baseWriter.WriteState;
public override XmlWriterSettings Settings => baseWriter.Settings;
public override XmlSpace XmlSpace => baseWriter.XmlSpace;
public override string XmlLang => baseWriter.XmlLang;
public override void Close() => baseWriter.Close();
public override void Flush() => baseWriter.Flush();
public override string LookupPrefix(string ns) => baseWriter.LookupPrefix(ns);
public override void WriteBase64(byte[] buffer, int index, int count)
baseWriter.WriteBase64(buffer, index, count);
public override void WriteCData(string text)
baseWriter.WriteCData(text);
public override void WriteCharEntity(char ch)
baseWriter.WriteCharEntity(ch);
public override void WriteChars(char[] buffer, int index, int count)
baseWriter.WriteChars(buffer, index, count);
public override void WriteComment(string text)
baseWriter.WriteComment(text);
public override void WriteDocType(string name, string pubid, string sysid, string subset)
baseWriter.WriteDocType(name, pubid, sysid, subset);
public override void WriteEndAttribute()
baseWriter.WriteEndAttribute();
public override void WriteEndDocument()
baseWriter.WriteEndDocument();
public override void WriteEndElement()
baseWriter.WriteEndElement();
public override void WriteEntityRef(string name)
baseWriter.WriteEntityRef(name);
public override void WriteFullEndElement()
baseWriter.WriteFullEndElement();
public override void WriteProcessingInstruction(string name, string text)
baseWriter.WriteProcessingInstruction(name, text);
public override void WriteRaw(string data)
baseWriter.WriteRaw(data);
public override void WriteRaw(char[] buffer, int index, int count)
baseWriter.WriteRaw(buffer, index, count);
public override void WriteStartAttribute(string prefix, string localName, string ns)
baseWriter.WriteStartAttribute(prefix, localName, ns);
public override void WriteStartDocument(bool standalone) => baseWriter.WriteStartDocument(standalone);
public override void WriteStartDocument() => baseWriter.WriteStartDocument();
public override void WriteStartElement(string prefix, string localName, string ns)
baseWriter.WriteStartElement(prefix, localName, ns);
public override void WriteString(string text)
baseWriter.WriteString(text);
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
baseWriter.WriteSurrogateCharEntity(lowChar, highChar);
public override void WriteWhitespace(string ws)
baseWriter.WriteWhitespace(ws);
public static class XNodeExtensions
public static string ToStringNoEndBracketEscaping(this XNode node, bool onlyForAttributes = false)
throw new ArgumentNullException(nameof(node));
using var textWriter = new StringWriter();
using (var innerWriter = XmlWriter.Create(textWriter, new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
using (var writer = new NoEndBracketEscapingXmlWriter(innerWriter, onlyForAttributes))
return textWriter.ToString();
public static void Test()
var xmlString = @"<?xml version=""1.0"" encoding=""utf-8""?><Foo Name=""a->b""></Foo>";
Assert.AreEqual(@"<Foo Name=""a->b""></Foo>", Test(xmlString));
Assert.AreEqual(@"<Foo Name=""]]>"">]]></Foo>", Test(@"<Foo Name=""]]>"">]]></Foo>"));
Assert.AreEqual(@"<Foo Name=""]]>"">></Foo>", Test(@"<Foo Name=""]]>"">></Foo>"));
Assert.AreEqual(@"<Foo Name=""]]>"">></Foo>", Test(@"<Foo Name=""]]>"">></Foo>", true));
Assert.AreEqual(@"<Foo Name=""""></Foo>", Test(@"<Foo Name=""""></Foo>"));
public static string Test(string xmlString, bool onlyForAttributes = false)
Console.WriteLine("\nInput document:");
Console.WriteLine(xmlString);
var doc = XDocument.Parse(xmlString);
foreach (var element in doc.Root.DescendantsAndSelf())
Console.Write("Attributes for element {0}: ", element.Name);
foreach (var attr in doc.Root.Attributes())
Console.Write("\"{0}\": \"{1}\"{2}", attr.Name, attr.Value, i++ == 0 ? "" : ", ");
var newXml = doc.ToStringNoEndBracketEscaping(onlyForAttributes);
Console.WriteLine(onlyForAttributes ? "Re-serialized XDocument without > escaping for attributes only:" : "Re-serialized XDocument without > escaping:");
Console.WriteLine(newXml);
Assert.DoesNotThrow(() => XDocument.Parse(newXml));
public static void Main()
Console.WriteLine("Environment version: {0} ({1}, {2})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("Failed with unhandled exception: ");