Reputation: 14873
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?
Upvotes: 5
Views: 2603
Reputation: 125689
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;
Upvotes: 2