saveoptions.none会删除格式性空白节点但保留显式字符串;真正保留缩进需加载时用loadoptions.preservewhitespace并保存时用saveoptions.disableformatting或format。

SaveOptions.None 会删除空白节点,但不是所有空白
SaveOptions.None 是 XDocument.Save 的默认选项,它会让序列化过程“忽略”某些空白文本节点——但仅限于那些被判定为「格式性空白」(即仅由空格、换行、制表符组成,且父元素有子元素时出现在元素之间)的节点。它不会动你显式写入的 " " 或 "\n" 字符串内容。
常见错误现象:XML 文件保存后缩进没了、换行消失了、相邻标签“粘连”在一起,比如 <name>Alice</name><age>30</age> 而不是分行显示。
- 这是预期行为,不是 bug ——
SaveOptions.None的设计目标就是紧凑输出 - 如果你用
XElement.Parse("<root>\n <a></a>\n <b></b>\n</root>")创建树,那些换行和缩进在解析阶段就已被当作格式空白丢弃,SaveOptions.None只是不额外加回来 - 真正保留原始缩进需用
SaveOptions.DisableFormatting,但它只对「已存在」的空白节点起作用;如果解析时空白已丢失,保存时也救不回来
想保留换行缩进?别只靠 SaveOptions
仅设置 SaveOptions.Format 不足以让 XML 看起来“美观”。它只控制是否在元素间插入换行和缩进,前提是:树中已有足够的空白节点支撑格式化逻辑。
使用场景:调试输出、生成人可读配置文件、和外部系统做 diff 对比。
-
SaveOptions.Format会在每个元素开始标签后、结束标签前插入换行+缩进,但不会在文本内容里插空格 - 若你用
new XElement("root", new XElement("a"), new XElement("b"))构造,没手动加XText("\n "),那即使选Format,结果仍是紧凑的 - 真正可控的方式是:构造时显式插入格式节点,或用
XDocument.ToString(SaveOptions.Format)(注意:ToString()不走Save()流程,行为略有差异)
SaveOptions.DisableFormatting 和 Format 的关键区别
SaveOptions.DisableFormatting 不是“关闭格式化”,而是“禁用自动格式化逻辑”——它原样保留你在 XML 树中手动添加的所有 XText 节点,包括空格、换行、甚至不可见字符。
而 SaveOptions.Format 是主动注入格式,不管原来有没有。
- 用
DisableFormatting前,必须确保你构造树时已经插入了想要的空白,例如:new XElement("root", "\n ", new XElement("a"), "\n ", new XElement("b"), "\n") -
Format在保存时动态计算缩进层级,适合快速美化,但无法控制具体缩进宽度(固定为 2 空格) - 两者都不影响属性顺序、命名空间声明位置等底层细节;这些由
XDocument内部状态决定,无法通过SaveOptions调整
常见误用:以为 SaveOptions 能修复解析时丢失的空白
很多人试图用 SaveOptions.Format 把从文件读进来的 XML “变好看”,却发现没变化。问题往往出在解析阶段:用 XDocument.Load(path) 默认启用 LoadOptions.PreserveWhitespace 吗?不,默认是 None,也就是丢弃格式空白。
- 正确做法:加载时明确指定
LoadOptions.PreserveWhitespace,例如:XDocument.Load(path, LoadOptions.PreserveWhitespace) - 否则,即使后续用
Format保存,也只能格式化“现有结构”,没有空白节点可格式化 - 验证方法:断点查看
doc.Root.Nodes(),里面是否有XText类型节点,且Value包含换行符
空白处理是分两段的事:加载时留不留,保存时加不加。SaveOptions 只管后半段,前半段得自己把关。










