使用XPath遍历XML文档时如何避免输出重复节点值
问题根源
你遇到的两个核心问题导致输出不符合预期:
- 逻辑重复执行:
CorrectingDataBlock属于复杂类型,你代码中获取到它的匹配结果(共2个实例)后,循环2次调用processElement,每次调用都会重新遍历它下面的CurrentVersionData和NewVersionData,相当于叶子节点打印逻辑执行了2次,所以原本4条结果变成了8条。 - 输出顺序错误:你每次查询叶子节点都是从XML根节点出发,一次性取所有
CorrectingDataBlock下的CurrentVersionData,再一次性取所有NewVersionData,所以输出顺序是所有Current的值→所有New的值,而不是按每个CorrectingDataBlock内部的Current→New的顺序输出。
修复方案
核心修改逻辑:处理可重复的复杂类型节点时,先遍历每个节点实例,再以当前实例为上下文查询它的子节点,而不是每次都从根节点查询所有子节点,同时删掉重复的递归调用逻辑。
修改后的完整代码如下:
export class Parser { public _schemeDocument: Document; public _dataDocument: Document; // 新增contextNode参数,默认从根节点开始查询 public processElement(element: Element, dataXPath: string, contextNode: Node = this._dataDocument): void { const typeName: string = this._getElementTypeName(element); const typeElement: Element = this._getTypeElementByName(typeName); const currentXPath = dataXPath + `/*[local-name()='${element.getAttribute("name")}']`; // 先拿到当前层级的所有节点实例 const currentSnapshot: XPathResult = this._dataDocument.evaluate(currentXPath, contextNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); if (typeElement && this._isComplexType(typeElement)) { const sequence = this._schemeDocument.evaluate("./*[local-name()='sequence']", typeElement).iterateNext(); if (sequence) { // 遍历当前层级的每个节点实例,分别处理子元素,保证顺序 for (let i = 0; i < currentSnapshot.snapshotLength; i++) { const currentInstance = currentSnapshot.snapshotItem(i) as Element; Array.prototype.forEach.call((sequence as Element).children, (childElement: Element) => { if (this._isElement(childElement)) { const childTypeName: string = this._getElementTypeName(childElement); const childTypeElement: Element = this._getTypeElementByName(childTypeName); if (childTypeElement && this._isComplexType(childTypeElement)) { // 复杂类型递归处理,传入当前实例作为上下文,路径传当前层级的XPath this.processElement(childElement, currentXPath, currentInstance); } else { // 叶子节点,以当前实例为上下文查询,只会拿到当前实例下的子节点值 const childXPath = `./*[local-name()='${childElement.getAttribute("name")}']`; const childResult = this._dataDocument.evaluate(childXPath, currentInstance, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); const childElementCaption: string = this._getElementCaption(childElement); for (let j = 0; j < childResult.snapshotLength; j++) { const node = childResult.snapshotItem(j) as Element; const childElementValue = node ? node.textContent : "EMPTY"; console.log(childElementValue); } } } }); } } } } private _getElementTypeName(element: Element): string { const splittedTypeName: string[] = element.getAttribute("type").split(":"); return splittedTypeName.length > 1 ? splittedTypeName[1] : splittedTypeName[0]; } private _getTypeElementByName(typeName: string): Element { const simpleTypeXPath = `//*[local-name()='simpleType'][@name='${typeName}']`; const complexTypeXPath = `//*[local-name()='complexType'][@name='${typeName}']`; return this._schemeDocument.evaluate(`${simpleTypeXPath}|${complexTypeXPath}`, this._schemeDocument).iterateNext() as Element; } private _getElementCaption(element: Element): string { const elementCaption: Node = this._schemeDocument.evaluate(".//*[local-name()='documentation']", element).iterateNext(); return elementCaption ? elementCaption.textContent : "EMPTY"; } private _isComplexType(element: Element): boolean { return element.localName === "complexType"; } private _isElement(element: Element): boolean { return element.localName === "element"; } }
调用时传入XSD根元素和空路径即可:
// 假设rootXsdElement是XSD里的request元素 parser.processElement(rootXsdElement, "");
修改后输出结果完全符合预期:
current new 100 200
内容的提问来源于stack exchange,提问作者Artur Vakhitov
相关产品推荐
相关产品推荐

