如何在加载XDocument时保留节点值中的 换行符
.NET XDocument加载时保留
\r\n换行符方案 问题复现
执行代码var doc = XDocument.Parse("<r>a\r\nb</r>")后,读取doc.Root.Value得到的结果为a\nb,而非预期的a\r\nb。测试Parse与Load的所有常用重载,分别通过XmlReader、Stream、TextReader方式加载,都无法原样保留\r\n,最终输出始终只有\n,测试环境为Windows平台.NET 6。
公开讨论中提到的LoadOptions.PreserveWhitespace参数,实测在调用XDocument.Load/XDocument.Parse时传入也无法解决该问题。
原因说明
这个表现不是.NET的实现bug,是W3C XML 1.0规范的强制要求:所有符合标准的XML解析器,处理文本节点时必须把独立\r、\r\n组合统一归一化为单个\n字符。PreserveWhitespace参数的作用只是控制是否保留元素间无意义的空白节点,不会突破规范修改换行归一化逻辑。
可用解决方法
- 方法1:自定义XmlReader拦截文本内容(无需修改原始XML)
通过自定义包装类重写XmlReader的文本读取逻辑,把解析过程中被归一化为\n的换行还原为\r\n,参考实现:
调用示例:// 需要引用System.Xml.XPath命名空间,使用内置的XmlWrappingReader简化包装逻辑 public class KeepCrLfXmlReader : XmlWrappingReader { public KeepCrLfXmlReader(XmlReader baseReader) : base(baseReader) {} public override string Value => base.Value.Replace("\n", "\r\n"); public override string ReadString() => base.ReadString().Replace("\n", "\r\n"); // 重写属性读取方法,避免属性值中的换行也被归一化 public override string GetAttribute(int i) => base.GetAttribute(i).Replace("\n", "\r\n"); public override string GetAttribute(string name) => base.GetAttribute(name).Replace("\n", "\r\n"); public override string GetAttribute(string name, string ns) => base.GetAttribute(name, ns).Replace("\n", "\r\n"); }
如果不想依赖using var sr = new StringReader("<r>a\r\nb</r>"); using var baseReader = XmlReader.Create(sr, new XmlReaderSettings { IgnoreWhitespace = false }); using var crlfReader = new KeepCrLfXmlReader(baseReader); var doc = XDocument.Load(crlfReader, LoadOptions.PreserveWhitespace); // 此时doc.Root.Value输出即为预期的a\r\nbXmlWrappingReader,直接继承XmlReader抽象类实现全量包装逻辑即可。 - 方法2:加载完成后批量替换文本节点(逻辑最简单)
如果业务场景中XML文本节点不存在需要单独保留的单个\n,可以在文档加载完成后遍历所有文本节点做替换:var doc = XDocument.Parse("<r>a\r\nb</r>", LoadOptions.PreserveWhitespace); foreach (var textNode in doc.DescendantNodes().OfType<XText>()) { textNode.Value = textNode.Value.Replace("\n", "\r\n"); } - 方法3:生成XML时对
\r做实体编码(从源头处理)
如果可以控制XML的生成端,生成时把所有\r转义为实体
,解析器就会保留该回车符不会做归一化:// 原始a\r\nb编码后对应XML内容为<r>a
\nb</r> var doc = XDocument.Parse("<r>a
\nb</r>"); // 读取doc.Root.Value可直接得到a\r\nb
注意点
- 调整
XmlReaderSettings里的换行处理相关属性不会生效,该配置不针对XML文本节点的规范级归一化逻辑 - 所有方案都需要开启
LoadOptions.PreserveWhitespace,否则解析器会直接修剪文本首尾、元素间隙的空白,无法保留原始换行格式
内容的提问来源于stack exchange,提问作者Martin
相关产品推荐
相关产品推荐

