You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在加载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\nb
    
    如果不想依赖XmlWrappingReader,直接继承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转义为实体&#xD;,解析器就会保留该回车符不会做归一化:
    // 原始a\r\nb编码后对应XML内容为<r>a&#xD;\nb</r>
    var doc = XDocument.Parse("<r>a&#xD;\nb</r>");
    // 读取doc.Root.Value可直接得到a\r\nb
    

注意点

  • 调整XmlReaderSettings里的换行处理相关属性不会生效,该配置不针对XML文本节点的规范级归一化逻辑
  • 所有方案都需要开启LoadOptions.PreserveWhitespace,否则解析器会直接修剪文本首尾、元素间隙的空白,无法保留原始换行格式

内容的提问来源于stack exchange,提问作者Martin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 23:36:07