如何从XPath选择中获取IXMLNodeList?

5
我发现了一篇关于XPath和Delphi TXmlDocument的问题(链接1)
虽然该答案可以用于选择单个xml节点,但我想使用它来选择一个节点列表。
我找到了一个类似的实用函数,据说可以完美地实现这一功能,但它不能正常工作。
这个看起来有缺陷的函数:
uses
  Xml.Xmldom, Xml.XMLIntf, Xml.XMLDoc;

function SelectNodes(xnRoot: IXmlNode; const nodePath: WideString): IXMLNodeList;
var
  intfSelect : IDomNodeSelect;
  intfAccess : IXmlNodeAccess;
  dnlResult  : IDomNodeList;
  intfDocAccess : IXmlDocumentAccess;
  doc: TXmlDocument;
  i : Integer;
  dn : IDomNode;
begin
  Result := nil;
  if not Assigned(xnRoot)
    or not Supports(xnRoot, IXmlNodeAccess, intfAccess)
    or not Supports(xnRoot.DOMNode, IDomNodeSelect, intfSelect) then
    Exit;

  dnlResult := intfSelect.selectNodes(nodePath);
  if Assigned(dnlResult) then
  begin
    Result := TXmlNodeList.Create(intfAccess.GetNodeObject, '', nil);
    if Supports(xnRoot.OwnerDocument, IXmlDocumentAccess, intfDocAccess) then
      doc := intfDocAccess.DocumentObject
    else
      doc := nil;

    for i := 0 to dnlResult.length - 1 do
    begin
      dn := dnlResult.item[i];
      Result.Add(TXmlNode.Create(dn, nil, doc));
    end;
  end;
end;

这是一个简化版本,不使用IXMLNodeList,而是直接使用“原始”的IDomNodeList

function SimpleSelectNodes(xnRoot: IXmlNode; const nodePath: WideString): IDOMNodeList;
var
  intfSelect : IDomNodeSelect;
begin
  Result := nil;
  if not Assigned(xnRoot)
    or not Supports(xnRoot.DOMNode, IDomNodeSelect, intfSelect) then
    Exit;

  Result := intfSelect.selectNodes(nodePath);
end;

测试代码:

procedure TForm1.FormCreate(Sender: TObject);
var
  Doc: IXMLDocument;
  Root: IXMLNode;
  DomNodeList: IDomNodeList;
  XmlNodeList: IXMLNodeList;
  XmlNode : IXMLNode;
  I: Integer;
begin
  // Build a test DOM tree in memory
  Doc := NewXMLDocument;
  Root := Doc.AddChild('root');
  Root.AddChild('C1');
  Root.AddChild('C2');
  Root.AddChild('C3');

  // Select using the IDomNodeList interface
  DomNodeList := SimpleSelectNodes(Root, '/root/*');
  for I := 0 to DomNodeList.length - 1 do
    ShowMessage(DomNodeList.item[I].nodeName);

  // Select using the IXMLNodeList interface
  XmlNodeList := SelectNodes(Root, '/root/*');
  XmlNode := XmlNodeList.First;
  while XmlNode <> nil do
  begin
    ShowMessage(XmlNode.NodeName);
    XmlNode := XmlNode.NextSibling;
  end;
end;

尽管SimpleSelectNodes版本运行良好,但SelectNodes函数并不起作用。

它返回一个IXMLNodeList,但当我尝试实际遍历此列表时,我只得到第一项,NextSiblingnil

如何让IXMLNodeList正常工作?


你是在寻找节点列表中的下一个项目,还是需要列表中第一个节点的下一个兄弟节点(在你的示例中它们是相同的,但并不总是这样)? - whosrdaddy
第一个节点没问题,但是下一个节点无法使用NextSibling函数访问。 - Jens Mühlenhoff
1个回答

2
我遇到了同样的问题(基本上是相同的代码)。
我能解决它的唯一方法是按索引迭代节点,因为NextSibling每次都会返回nil。像这样的东西可以起作用:
var
  i: Integer;
  Nodes: IXMLNodeList;
  Node: IXMLNode;
begin
  Nodes := SelectNodes(...);
  for i := 0 to NodeList.Count - 1 do
  begin
    Node := NodeList.Nodes[i];
    // Process node
  end;
end;

我认为Jens想要原始文档节点? - whosrdaddy
@whosrdaddy:抱歉,我不理解这个评论。NextSibling无法工作,而问题(在最后几句话中)是“... NextSibling为nil。我该如何让IXMLNodeList工作?” - Ken White
http://docwiki.embarcadero.com/Libraries/XE2/en/Xml.XMLDoc.TXMLNode.NextSibling 表示它使用父节点的ChildNodes属性。在我的简单示例中可能有效,但当列表的内容具有不同的父项时,它不可能起作用。 - Jens Mühlenhoff
1
整个事情有点像折中的解决办法,原始类和接口并不支持XPath选择,这有点令人遗憾。 - Jens Mühlenhoff
@Jens:是的,我认为这是他们实现IXMLDocument的方式所致,以保持它不会因跨平台原因而被绑定到特定的实现上。 - Ken White

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接