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

使用XmlNode.SelectNodes查询文本节点时仅返回首个匹配项的问题

XPath text() 查询移除节点后的异常原因分析

问题描述

原始XML结构:<label>Matrise <foo/><foo/>GM:1<foo/></label>

  • 初始状态:通过labelNode.SelectNodes("text()")可正常获取两个文本节点:Matrise 和GM:1
  • 修改后状态:移除所有<foo>节点后,labelNode.ChildNodes仍显示存在两个独立的文本节点,但text()查询仅返回第一个节点,出现结果矛盾。

复现代码

using System.Xml;
using System.Linq;

var xml = "<label>Matrise <foo/><foo/>GM:1<foo/></label>";
var doc = new XmlDocument();
doc.LoadXml(xml);
var labelNode = doc.SelectSingleNode("label");

Console.WriteLine("Before modifications:");
PrintTextNodesInOriginal(labelNode);

Console.WriteLine("\r\nAfter modifications:");
PrintTextNodesInModified(labelNode);


void PrintTextNodesInOriginal(XmlNode labelNode)
{
    PrintTextNodes(labelNode);
}

void PrintTextNodesInModified(XmlNode labelNode)
{
    // Remove foo nodes
    var foos = labelNode
        .SelectNodes("foo")
        .Cast<XmlNode>()
        .ToList();

    foreach (var foo in foos)
    {
        foo.ParentNode.RemoveChild(foo);
    }

    PrintTextNodes(labelNode);
}

List<XmlNode> GetTextNodes(XmlNode labelNode)
{
    return labelNode.SelectNodes("text()")
        .Cast<XmlNode>()
        .ToList();
}

void PrintTextNodes(XmlNode labelNode)
{
    var textNodes = GetTextNodes(labelNode);
    var textNodesActual = labelNode.ChildNodes
        .Cast<XmlNode>()
        .Where(node => node.NodeType == XmlNodeType.Text)
        .ToList();

    Console.WriteLine($"Nodes count using query: {textNodes.Count}");
    Console.WriteLine($"Nodes count actual: {textNodesActual.Count}");
    Console.WriteLine("Nodes' contents:");

    foreach (var textNode in textNodes)
    {
        Console.WriteLine(textNode.InnerText);
    }
}

预期结果

Before modifications:
Nodes count using query: 2
Nodes count actual: 2
Nodes' contents:
Matrise 
GM:1

After modifications:
Nodes count using query: 2
Nodes count actual: 2
Nodes' contents:
Matrise 
GM:1

实际结果

Before modifications:
Nodes count using query: 2
Nodes count actual: 2
Nodes' contents:
Matrise 
GM:1

After modifications:
Nodes count using query: 1
Nodes count actual: 2
Nodes' contents:
Matrise 

原因解析

这个矛盾源于XmlDocument的DOM节点模型与XPath查询的规范差异:

  1. DOM节点模型的特性:XmlDocument在移除节点后,不会自动合并相邻的文本节点。因此移除<foo>后,原来被分隔的两个文本节点变成相邻的兄弟节点,但物理上仍然是两个独立的节点,这就是ChildNodes能检测到两个节点的原因。
  2. XPath查询的规范遵循:XPath 1.0的text()函数查询时,严格遵循XML的「字符数据归一化」规则——相邻的文本节点在逻辑上会被视为一个合并后的节点。因此即使DOM中存在两个物理文本节点,XPath查询只会返回第一个,其余相邻的会被忽略。

解决方法

如果需要让XPath查询与DOM节点的实际状态一致,可在移除节点后手动调用XmlNode.Normalize()方法,该方法会将所有相邻的文本节点合并为单个节点:

void PrintTextNodesInModified(XmlNode labelNode)
{
    // Remove foo nodes
    var foos = labelNode
        .SelectNodes("foo")
        .Cast<XmlNode>()
        .ToList();

    foreach (var foo in foos)
    {
        foo.ParentNode.RemoveChild(foo);
    }

    // 手动合并相邻文本节点
    labelNode.Normalize();

    PrintTextNodes(labelNode);
}

内容的提问来源于stack exchange,提问作者Jānis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 18:14:56