5

I came across a question about XPath and Delphi TXmlDocument.

While the answer works fine for selecting a single xml node, I wanted to use it to select a list of nodes.

I found a similar utility function that is supposed to do exactly that, but it does not work correctly.

The apparently buggy function:

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;

A simplified version that does not use a IXMLNodeList, but the "raw" IDomNodeList instead:

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;

Test code:

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;

While the SimpleSelectNodes version works fine, the SelectNodes function does not.

It returns a IXMLNodeList, but when I try to actually iterate through this list I only get the first item, the NextSibling is nil.

How can I get the IXMLNodeList to work?

Community
  • 1
  • 1
Jens Mühlenhoff
  • 14,565
  • 6
  • 56
  • 113
  • are you looking for the next item in the nodelist or do need the next sibling of the first node in the list (in your example, they are the same, but that will not always be the case)? – whosrdaddy Apr 28 '14 at 15:21
  • The first node is ok, but the next node can not be accessed using the NextSibling function. – Jens Mühlenhoff Apr 28 '14 at 21:21

1 Answers1

2

I had the same issue (with basically the same code).

The only way I could resolve it was to iterate through the nodes by index, as NextSibling returns nil for some reason every time. Something like this works:

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;
Ken White
  • 123,280
  • 14
  • 225
  • 444
  • I think that Jens wants the original document node? – whosrdaddy Apr 28 '14 at 14:03
  • @whosrdaddy: Sorry -I don't understand the comment. NextSibling doesn't work, and the question (in the last couple of sentences) is "...NextSibling is nil. How can I get the IXMLNodeList to work?" – Ken White Apr 28 '14 at 14:07
  • http://docwiki.embarcadero.com/Libraries/XE2/en/Xml.XMLDoc.TXMLNode.NextSibling says that it uses the parent nodes ChildNodes property. In my simple example that could work, but when the contents of the list have different parents it can not possibly work. – Jens Mühlenhoff Apr 28 '14 at 21:24
  • 1
    The whole thing is kind of a hack, the original classes and interfaces were not made to support XPath selection which is a bit sad. – Jens Mühlenhoff Apr 28 '14 at 21:25
  • @Jens: Yeah, I think that's a result of the way they implemented IXMLDocument to keep it from being tied to a specific implementation for xplatform reasons. – Ken White Apr 28 '14 at 21:54