如何阻止XmlDocument加载XML时自动解码HTML十进制实体,保留原始编码格式
关于XmlDocument自动解码十进制实体的问题解决方案
问题本质
&#xxxx;形式的十进制实体在调用LoadXml时被自动解码为实际字符,是符合XML 1.0规范的默认设计行为,没有官方提供的配置项可以直接关闭这个解析流程。
原因是XmlDocument基于标准DOM模型实现,DOM规范明确要求节点存储的是实体解析后的实际Unicode字符值,原始的实体语法文本在文档加载完成后就不会被保留,因此无论加载还是序列化阶段,都不存在“跳过实体解码、保留原始实体文本”的原生开关。
可行的落地实现方案
1. 自定义序列化流程,按规则将输出字符转回十进制实体格式
不需要修改加载阶段的逻辑,只需要在将修改后的DOM序列化为输出字符串时,拦截输出流程,将对应需要保留为实体格式的字符(比如解码回来的欧元符号)替换为对应的&#xxxx;十进制实体,完全对齐原始输入的文本格式。
核心实现是重写XmlWriter的字符串输出逻辑,代码示例:
public class DecimalEntityPreservingXmlWriter : XmlTextWriter { public DecimalEntityPreservingXmlWriter(TextWriter writer) : base(writer) { // 保持和原XML一致的缩进格式,避免序列化后格式出现非预期偏差 Formatting = Formatting.Indented; } public override void WriteString(string text) { var outputBuilder = new StringBuilder(); foreach (char c in text) { // 可根据需求调整判断规则,对非ASCII字符统一替换为十进制实体格式输出 if (c > 127) { outputBuilder.Append($"&#{(int)c};"); } else { outputBuilder.Append(c); } } base.WriteString(outputBuilder.ToString()); } }
替换原有序列化逻辑的调用方式:
XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xmlString); // 原有节点定位、修改的逻辑完全保持不变 XmlNode node = xmlDoc.SelectSingleNode(".//var[@name='DecimalSeparator']"); node.Attributes["value"].Value = "."; // 替换原来直接读取xmlDoc.OuterXml的逻辑 using var stringWriter = new StringWriter(); using var customWriter = new DecimalEntityPreservingXmlWriter(stringWriter); xmlDoc.WriteTo(customWriter); string modified = stringWriter.ToString();
这种方案的通用性远优于手动替换单个字符的临时方案:不管原始XML里的实体对应什么特殊字符,只要是从十进制实体解码回来的非ASCII字符,都会被自动按照&#编码;的格式重新输出,不存在需要手动维护映射表的覆盖盲区。
2. 替换DOM类组件,使用流处理模式全程保留原始文本
如果希望从根源上避免实体解码带来的内容偏差,可以放弃XmlDocument这类全量DOM解析组件,改用XmlReader的逐行流处理模式,核心逻辑是:
- 加载XML时直接保留原始文本流,不做实体解析
- 通过流定位到需要修改的节点位置,直接在原始文本上做定点替换
- 输出时直接输出修改后的原始文本流,全程不经过DOM层的解码流程
这种方式完全不会改变原始XML除目标修改节点外的任何内容,连原有的排版格式、实体写法都和输入完全一致,适合代理服务这类对输出一致性要求极高的场景。
常见误区
不要尝试通过修改XmlResolver配置、自定义实体字典的方式阻止字符实体解码:这类配置只能控制外部引用实体的加载行为,无法修改内置字符实体的默认解析逻辑,调整成本高且无法达到预期效果。
内容的提问来源于stack exchange,提问作者XavierA
相关产品推荐
相关产品推荐

