正如我所说,这是一种痛苦。 XmlReader 和 XmlTextReader(这是 XmlDocument 和几乎所有其他“高级” .NET xml 类使用的阅读器)会自动取消转义它们遇到的所有 xml char 实体,并且无法禁用此功能。因此,除非重写一大块 XmlReader,否则不可能保留原始的 xml 字符实体。可以做的是在XmlWriter 方面工作。还是很痛。
public class EscapedXmlWriter : XmlWriter
{
// Note that normally XmlWriter uses hex escapes
public bool UseHexCharEntities { get; set; } = false;
protected XmlWriter Writer;
public EscapedXmlWriter(XmlWriter writer)
{
Writer = writer;
}
public override System.Xml.WriteState WriteState => Writer.WriteState;
public override void Flush() => Writer.Flush();
public override string LookupPrefix(string ns) => Writer.LookupPrefix(ns);
public override void WriteBase64(byte[] buffer, int index, int count) => Writer.WriteBase64(buffer, index, count);
public override void WriteCData(string text) => Writer.WriteCData(text);
public override void WriteCharEntity(char ch) => Writer.WriteCharEntity(ch);
public override void WriteChars(char[] buffer, int index, int count) => Writer.WriteChars(buffer, index, count);
// We could give to comments the same treatment of attributes and content
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() => Writer.WriteEndAttribute();
public override void WriteEndDocument() => Writer.WriteEndDocument();
public override void WriteEndElement() => Writer.WriteEndElement();
public override void WriteEntityRef(string name) => 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(char[] buffer, int index, int count) => Writer.WriteRaw(buffer, index, count);
public override void WriteRaw(string data) => Writer.WriteRaw(data);
public override void WriteStartAttribute(string prefix, string localName, string ns) => Writer.WriteStartAttribute(prefix, localName, ns);
public override void WriteStartDocument() => Writer.WriteStartDocument();
public override void WriteStartDocument(bool standalone) => Writer.WriteStartDocument(standalone);
public override void WriteStartElement(string prefix, string localName, string ns) => Writer.WriteStartElement(prefix, localName, ns);
public override void WriteString(string text)
{
Trace.WriteLine($"{Writer.WriteState}: {text}");
var isAttribute = Writer.WriteState == System.Xml.WriteState.Attribute;
var sb = new StringBuilder();
for (int i = 0; i < text.Length; i++)
{
char ch = text[i];
char low;
if (ch == '"' && isAttribute)
{
sb.Append(""");
}
if (ch == '\'' && isAttribute)
{
// XmlWriter doesn't escape ' in attributes because it always uses the form "xyz" instead of the form 'xyz'
sb.Append("'");
}
else if (ch == '&')
{
sb.Append("&");
}
else if (ch == '<')
{
sb.Append("<");
}
else if (ch == '>')
{
sb.Append(">");
}
else if (i + 1 < text.Length && char.IsHighSurrogate(ch) && char.IsLowSurrogate(low = text[i + 1]))
{
int utf32 = char.ConvertToUtf32(ch, low);
if (UseHexCharEntities)
{
sb.Append("&#x");
sb.Append(utf32.ToString("X"));
}
else
{
sb.Append("&#");
sb.Append(utf32);
}
sb.Append(';');
i++;
}
else if (ch < 0x20 || ch > 0x7f)
{
if ((ch == '\r' || ch == '\n' || ch == '\t') && !isAttribute)
{
sb.Append(ch);
}
else
{
if (UseHexCharEntities)
{
sb.Append("&#x");
sb.Append(((int)ch).ToString("X"));
}
else
{
sb.Append("&#");
sb.Append((int)ch);
}
sb.Append(';');
}
}
else
{
sb.Append(ch);
}
}
string text2 = sb.ToString();
Writer.WriteRaw(text2);
}
public override void WriteSurrogateCharEntity(char lowChar, char highChar) => Writer.WriteSurrogateCharEntity(lowChar, highChar);
public override void WriteWhitespace(string ws) => Writer.WriteWhitespace(ws);
protected override void Dispose(bool disposing)
{
if (disposing)
{
Writer.Dispose();
}
base.Dispose(disposing);
}
}
这是一个EscapedXmlWriter,它将转义所有字符< 0x20 或> 0x7f(因此它将转义任何控制字符或非ASCII 字符)。它将在内容中保留新的行/换行符/标签,否则内容将变得难以辨认。甚至还有对非 BMP 字符的特殊处理(例如?)。并且有一个选项UseHexCharEntities 来控制是否应该使用十六进制或十进制转义。
像这样使用它:
using (var writer = new EscapedXmlWriter(XmlWriter.Create("out.xml", new XmlWriterSettings
{
Indent = true,
})))
{
var doc = new XmlDocument();
doc.Load("XMLFile1.xml");
// THIS!
doc.WriteTo(writer);
}
XmlWriter.Create() 非常灵活。您可以将其“连接”到 StringBuilder 或 Stream 或 StringWriter。
var sb = new StringBuilder();
XmlWriter.Create(sb, new XmlWriterSettings { ... })