使用宽容模式解析不规范XML,结合XmlReader设置与HtmlAgilityPack,预处理清洗数据并防御性提取关键内容。

处理格式不规范的XML是实际开发中常遇到的问题,尤其在对接第三方系统或读取老旧数据时。C# 提供了多种方式来解析 XML,但在面对标签未闭合、属性缺失引号、编码错误等混乱情况时,默认解析器容易抛出异常。这时候需要启用“宽容模式”并结合合适的方法提取有效数据。
使用XmlReader配置宽容选项
标准的 XmlDocument 和 XDocument 在遇到非法 XML 时会直接报错,但 XmlReader 允许通过设置 XmlReaderSettings 来放宽验证规则。
虽然 .NET 原生并不支持像 HTML 解析器那样的完全容错(如 AngleSharp 或 HtmlAgilityPack),但可以配合外部库或调整输入提升鲁棒性。
- 设置
CheckCharacters = false忽略非法字符 - 关闭
ValidateOnParse = false防止 DTD 验证中断解析 - 启用
IgnoreWhitespace = true减少因空格导致的结构误判
// 示例:创建宽容的 XmlReader
var settings = new XmlReaderSettings
{
CheckCharacters = false,
ValidateOnParse = false,
IgnoreWhitespace = true,
IgnoreComments = true,
IgnoreProcessingInstructions = true
};
using var reader = XmlReader.Create(streamOrPath, settings);
var doc = new XmlDocument();
doc.Load(reader); // 尽量加载可识别部分
借助HtmlAgilityPack解析类XML内容
当 XML 混乱到接近 HTML 的程度(例如标签不闭合、嵌套错误),推荐使用 HtmlAgilityPack —— 它专为容忍语法错误而设计,也能用于非标准 XML。
- 安装 NuGet 包:HtmlAgilityPack
- 将混乱 XML 当作 HTML 文档加载
- 利用 XPath 提取节点,即使原始结构残缺
// 示例:用 HtmlAgilityPack 加载破损 XML
var htmlDoc = new HtmlAgilityPack.HtmlDocument(); htmlDoc.OptionFixNestedTags = true; // 自动修复嵌套 htmlDoc.OptionAutoCloseOnEnd = true; // 遇到结束标签自动补全 htmlDoc.OptionCheckSyntax = false; htmlDoc.Load(filePath); // 即使格式错误也能读入// 使用类似 XPath 的方式提取数据 var nodes = htmlDoc.DocumentNode.SelectNodes("//item"); foreach (var node in nodes) { var id = node.GetAttributeValue("id", ""); var name = node.SelectSingleNode("name")?.InnerText.Trim(); Console.WriteLine($"{id}: {name}"); }
预处理清洗脏数据
在解析前对原始文本做简单清洗,能显著提高成功率。
- 移除非法控制字符(如 ASCII 0-8, 11-12, 14-31)
- 补全明显缺失的引号或尖括号(谨慎使用)
- 替换
&为&防止实体解析失败 - 将自闭合标签(如
)规范化为
// 清洗示例
string CleanXmlString(string input)
{
// 移除非法 XML 字符
var cleaned = Regex.Replace(input, @"[\x00-\x08\x0B\x0C\x0E-\x1F]", "");
// 修复孤立的 &
cleaned = Regex.Replace(cleaned, @"&(?![a-zA-Z#][a-zA-Z0-9#]*;)", "&");
// 补全常见自闭合标签(视需求扩展)
cleaned = Regex.Replace(cleaned, @"<(img|br|hr|input|meta|link)\b([^>]*)>",
@"<$1$2 />");
return cleaned;}
灵活提取与容错处理
即使成功加载文档,某些节点仍可能为空或结构异常。编写数据提取逻辑时应保持防御性。
- 始终检查节点是否为 null 再访问 InnerText 或 Attributes
- 使用 try-catch 包裹高风险转换(如 int.Parse)
- 记录解析日志,便于后续修正源头问题
基本上就这些。面对混乱 XML,关键是不要依赖严格标准,而是组合使用宽容读取、外部解析库和前置清洗,把“能救的数据”尽可能捞出来。很多时候不需要完美还原,只要关键字段可用即可。









