我碰到a question about XPath and Delphi TXmlDocument

尽管选择单个xml节点的答案很好,但我想用它来选择节点列表。

我发现了一个similar utility function应该可以做到这一点,但是它不能正常工作。

显然是 buggy 的功能:

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,而是使用“raw” 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正常工作?

最佳答案

我遇到了同样的问题(使用基本相同的代码)。

我能解决的唯一方法是按索引遍历节点,因为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;

10-07 22:08