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数据中散落大量
字符,直接使用DOMParser以text/xml类型解析会抛出错误。我最开始采用的临时方案是先用text/html解析器处理原始字符串,导出解析后的文本(该过程会自动转换 等特殊字符),再将结果传入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
相关产品推荐
相关产品推荐

