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

document.evaluate执行XPath返回嵌套节点结果的原因与解决方法

XPath查询XML返回嵌套节点问题排查

问题场景

我尝试使用XPath查询XML数据,以下是经过隐私脱敏修改的XML内容:

<xml xmlns:s="uuid:BDC6E3F0-11d1-65B3-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A2A3-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
<s:Schema id="RowsetSchema">
    <s:ElementType name="row" content="eltOnly">
        <s:AttributeType name="SEARCH_TYPE_COLUMN_ID" rs:number="1" rs:writeunknown="true">
            <s:datatype dt:type="number" dt:maxlength="-1" rs:maybenull="True"/>
        </s:AttributeType>
        <s:AttributeType name="COLUMN_ID" rs:number="2" rs:writeunknown="true">
            <s:datatype dt:type="number" dt:maxlength="-1" rs:maybenull="True"/>
        </s:AttributeType>
        <s:AttributeType name="DSPLY_NM" rs:number="3" rs:writeunknown="true">
            <s:datatype dt:type="string" dt:maxlength="-1" rs:maybenull="True"/>
        </s:AttributeType>
    </s:ElementType>
</s:Schema>
<rs:data>
    <z:row SEARCH_TYPE_COLUMN_ID="02" COLUMN_ID="1"   DSPLY_NM="One" />
    <z:row SEARCH_TYPE_COLUMN_ID="03" COLUMN_ID="37"  DSPLY_NM="Two"/>
    <z:row SEARCH_TYPE_COLUMN_ID="04" COLUMN_ID="38"  DSPLY_NM="Three"/>
    <z:row SEARCH_TYPE_COLUMN_ID="05" COLUMN_ID="39"  DSPLY_NM="Four"/>
    <z:row SEARCH_TYPE_COLUMN_ID="06" COLUMN_ID="23"  DSPLY_NM="Five"/>
    <z:row SEARCH_TYPE_COLUMN_ID="07" COLUMN_ID="24"  DSPLY_NM="Six"/>
</rs:data>
<errors count="0"/>
</xml>

我编写了如下兼容垫片(shim),用于适配老旧IE代码的selectNodes逻辑:

if (typeof MyNamespace.selectNodes === 'undefined') {
    MyNamespace.selectNodes = function(context, xp, namespace)  {
        if(!context)
        {
            throw new Error("selectNodes cannot have a null context");
        }

        var doc = (context.nodeType != 9 ? context.ownerDocument : context);

        if (typeof doc.evaluate !== 'undefined') {
            var nsresolver = null;
            if (namespace instanceof Object) {
                nsresolver = function(prefix) {
                    return namespace[prefix];
                };
            }

            const snapshot = doc.evaluate(xp, context, nsresolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
            return [...Array(snapshot.snapshotLength)].map((_, i) => snapshot.snapshotItem(i));
        } 

        throw new Error("no XPath engine found");
    };
}

执行查询的代码如下:

this.xmlDOM = (new DomParser()).parseFromString(myXmlAsString);
var result = MyNamespace.selectNodes(this.xmlDOM, '//rs:data/z:row', {
                                                                        s:   "uuid:BDC6E3F0-11d1-65B3-A2A3-00AA00C14882",
                                                                        dt:  "uuid:C2F41010-65B3-11d1-A2A3-00AA00C14882",
                                                                        rs:  "urn:schemas-microsoft-com:rowset",
                                                                        z:   "#RowsetSchema",
                                                                        xsl: "http://www.w3.org/1999/XSL/Transform"
                                                                     });

预期结果

返回包含6个平级节点元素的结果集,结构如下:

<z:row SEARCH_TYPE_COLUMN_ID="02" COLUMN_ID="1"   DSPLY_NM="One" />
<z:row SEARCH_TYPE_COLUMN_ID="03" COLUMN_ID="37"  DSPLY_NM="Two"/>
<z:row SEARCH_TYPE_COLUMN_ID="04" COLUMN_ID="38"  DSPLY_NM="Three"/>
<z:row SEARCH_TYPE_COLUMN_ID="05" COLUMN_ID="39"  DSPLY_NM="Four"/>
<z:row SEARCH_TYPE_COLUMN_ID="06" COLUMN_ID="23"  DSPLY_NM="Five"/>
<z:row SEARCH_TYPE_COLUMN_ID="07" COLUMN_ID="24"  DSPLY_NM="Six"/>

实际结果

仅返回1个节点元素,内部是多层嵌套的z:row结构:

<z:row SEARCH_TYPE_COLUMN_ID="02" COLUMN_ID="1"   DSPLY_NM="One" >
    <z:row SEARCH_TYPE_COLUMN_ID="03" COLUMN_ID="37"  DSPLY_NM="Two">
        <z:row SEARCH_TYPE_COLUMN_ID="04" COLUMN_ID="38"  DSPLY_NM="Three">
            <z:row SEARCH_TYPE_COLUMN_ID="05" COLUMN_ID="39"  DSPLY_NM="Four">
                <z:row SEARCH_TYPE_COLUMN_ID="06" COLUMN_ID="23"  DSPLY_NM="Five">
                    <z:row SEARCH_TYPE_COLUMN_ID="07" COLUMN_ID="24"  DSPLY_NM="Six">
                    </z:row>
                </z:row>
            </z:row>
        </z:row>
    </z:row>
</z:row>

核心疑问:该现象的产生原因是什么,如何才能获取预期的查询结果?

问题根本原因

在@martin-honnen的回答启发下,我追溯XML数据源后最终定位到问题根源:

  • 传入的原始XML存在编码问题:数据源使用HTML编码器而非XML编码器处理文本值,导致XML数据中散落大量&nbsp;字符,直接使用DOMParser以text/xml类型解析会抛出错误。我最开始采用的临时方案是先用text/html解析器处理原始字符串,导出解析后的文本(该过程会自动转换&nbsp;等特殊字符),再将结果传入parseFromString,以text/xml类型重新解析。
  • 上述方案存在未考虑到的解析规则差异:text/html解析器不支持非HTML标准内置标签的自闭合语法。尽管<br/>这类内置自闭合标签已经在HTML标准中存在数十年,但对于自定义命名空间标签(比如本例中的z:row),HTML解析器不会将<tag />识别为完整的自闭合元素,反而会把后续出现的同标签识别为当前节点的子节点,将原本平级的自闭合标签错误转换为嵌套结构。

例如原本平级的结构:

<row>
    <data />
    <data />
    <data />
</row>

经过HTML解析器处理后,会被转换为如下错误的嵌套结构:

<row>
    <data>
    <data>
    <data></data></data></data>
</row>

在这种被解析器错误篡改结构的XML文档上执行XPath查询,自然无法得到预期结果。

注:此处特别感谢@martin-honnen提醒我完成了之前忽略的验证步骤——我最开始主观判定实际运行的XML和测试用脱敏XML的解析结果完全一致,完全没发现标签闭合结构被解析器篡改的问题,最终顺着这个方向找到了正确的解决路径。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 23:51:23