Reputation: 371
I try to find a node with specific "node name", "attribute" and "attribute value". I use the recursive function below.
My XMLDocument has a node named 'TestNodeName' with attribute 'Format' with value '1'.
The function works fine the first time: returns the prower node.
When I call it a second time, it gives wrong results: returns a node that has a Format attribute having value 0.
Example of XML.
<mnode>
<TestNodeName ID="1" Format="0">
</TestNodeName>
<TestNodeName ID="2" Format="1">
</TestNodeName>
<TestNodeName ID="3" Format="0">
</TestNodeName>
<TestNodeName ID="4" Format="1">
</TestNodeName>
<TestNodeName ID="5" Format="0">
</TestNodeName>
</mnode>
End of XML
unit Unit4;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, XMLIntf, XMLDoc;
type
TForm4 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
//GLOBAL VARIABLES
var
Form4: TForm4;
XML:IXMLDocument;
mnode:IXMLNode;
s:string;
implementation
{$R *.dfm}
function RecursiveFindNode(ANode: IXMLNode; const SearchNodeName: string): IXMLNode;
var
I: Integer;
begin
if CompareText(ANode.NodeName, SearchNodeName) = 0 then
Result := ANode
else if not Assigned(ANode.ChildNodes) then
Result := nil
else begin
for I := 0 to ANode.ChildNodes.Count - 1 do
begin
Result := RecursiveFindNode(ANode.ChildNodes[I], SearchNodeName);
if Assigned(Result) then
Exit;
end;
end;
end;
function RecursiveFindNodeAttr(ANode: IXMLNode; const SearchNodeName: string; sAttr, sAttrVal:string): IXMLNode;
var
I: Integer;
sAttrFind: ixmlnode;
stext:string;
begin
sAttrFind:=ANode.AttributeNodes.FindNode(sAttr);
if sAttrFind<>nil then stext:=sAttrFind.Text else stext:='';
if (CompareText(ANode.NodeName, SearchNodeName)=0)and(CompareText(sAttrFind.NodeName, sAttr)=0)and(CompareText(stext, sAttrVal)=0) then
begin
Result := ANode;
end
else if not Assigned(ANode.ChildNodes) then
begin
Result := nil;
end
else begin
for I := 0 to ANode.ChildNodes.Count - 1 do
begin
Result := RecursiveFindNodeAttr(ANode.ChildNodes[I], SearchNodeName, sAttr, sAttrVal);
if Assigned(Result) then
begin
Exit;
end;
end;
end;
end;
procedure TForm4.FormCreate(Sender: TObject);
var
cnode,foundNode:IXMLNode; //<-- Problem here "foundNode" must be in global
begin
XML:= NewXMLDocument;
XML.LoadFromFile('C:\test.xml');
mnode:=XML.DocumentElement;
foundNode:=RecursiveFindNode(mnode,'TestNodeName');
//First time
cnode:=RecursiveFindNodeAttr(XML.DocumentElement,'TestNodeName','Format','1');
if cnode<>nil then
begin
cnode.Attributes['Format']:='5';
ShowMessage('ID='+cnode.Attributes['ID']);
end
else
ShowMessage('nil');
//Second time
foundNode:=RecursiveFindNodeAttr(XML.DocumentElement,'TestNodeName','Format','1');
if foundNode<>nil then
begin
foundNode.Attributes['Format']:='5';
ShowMessage('ID='+foundNode.Attributes['ID']);
end
else
ShowMessage('nil');
XML.SaveToFile('C:\test.xml');
end;
end.
After few test i finally found what caused wrong result of function. There was a similar recursive function. When i removed call of function all result was OK. RecursiveFindNode(ANode: IXMLNode; const SearchNodeName: string): IXMLNode;
In code bellow is call foundNode:=RecursiveFindNode(mnode,'TestNodeName'); The first call of RecursiveFindNodeAttr will give good result because of "cnode:=" The second call of RecursiveFindNodeAttr will give wrong result (ID=1) because i used same variable "foundNode:=RecursiveFindNodeAttr(..."
Finally when i moved "var foundNode:IXMLNode;" from Tform4 declaration to global the second call returned good result (ID=4)
I found another problem. when i use RecursiveFindNodeAttr in loop after replace all format="1" to format="5", the "foundNode" result remains "not nil" so loop will never end.
foundNode:=RecursiveFindNodeAttr(XML.DocumentElement,'TestNodeName','Format','1');
while foundNode<>nil do
begin
foundNode.Attributes['Format']:='5';
ShowMessage('ID='+foundNode.Attributes['ID']);
foundNode:=RecursiveFindNodeAttr(XML.DocumentElement,'TestNodeName','Format','1');
if foundNode=nil then ShowMessage('nil');
end;
Upvotes: 0
Views: 9576
Reputation: 371
I found already made function which work fine with IXMLDocument. Function returns list of nodes if match parameters. XML is same as above (the one with "UID" attributes)
//Declared funciton
function FindNodeList(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;
var
xlist:IXMLNodeList;
mnode:IXMLNode;
XML:IXMLDocument;
begin
XML:= NewXMLDocument;
XML.LoadFromFile('C:\test.xml');
mnode:=XML.DocumentElement;
//This will find all nodes in nodes and subnodes if node name is "TestNodeName" attribute Format="1"
xlist:=FindNodeList(mnode,'//TestNodeName[@Format="1"]');
for I := 0 to xlist.Count - 1 do
begin
//This will set Format value to "8"
xlist[i].Attributes['Format']:='8';
end;
end.
Upvotes: 3
Reputation: 371
It seems "if not Assigned(ANode.ChildNodes) then" is always assigned. when i changed it to "if not (ANode.HasChildNodes) then" it stopped loop forever. I added into function messages to display how exactly is function executed.
It seems it checks same node 2 times, 1st time in "if (CompareText(ANode.Node..." and second time in loop "for I := 0 to ANode.ChildNo...".
<mnode UID="main">
<TestNodeName UID="1" Format="5">
<TestNodeName UID="11" Format="5">
</TestNodeName>
<TestNodeName UID="12" Format="5">
</TestNodeName>
<TestNodeName UID="13" Format="5">
</TestNodeName>
</TestNodeName>
<TestNodeName UID="2" Format="5">
</TestNodeName>
<TestNodeName UID="3" Format="5">
<TestNodeName UID="31" Format="5">
</TestNodeName>
<TestNodeName UID="32" Format="5">
<TestNodeName UID="321" Format="1">
</TestNodeName>
<TestNodeName UID="322" Format="5">
</TestNodeName>
<TestNodeName UID="323" Format="1">
</TestNodeName>
</TestNodeName>
<TestNodeName UID="33" Format="5">
</TestNodeName>
</TestNodeName>
<TestNodeName UID="4" Format="5">
</TestNodeName>
</mnode>
function RecursiveFindNodeAttr(ANode: IXMLNode; const SearchNodeName: string; sAttr, sAttrVal:string): IXMLNode;
var
I: Integer;
begin
if (CompareText(ANode.NodeName, SearchNodeName)=0)and(ANode.Attributes[sAttr]=sAttrVal) then
begin
ShowMessage('Found! '+ANode.Attributes['UID']);
Result := ANode;
end
else
begin
ShowMessage('1st try='+ANode.Attributes['UID']);
if not (ANode.HasChildNodes) then
begin
ShowMessage('nil');
Result := nil;
end
else begin
for I := 0 to ANode.ChildNodes.Count - 1 do
begin
Result := RecursiveFindNodeAttr(ANode.ChildNodes[I], SearchNodeName, sAttr, sAttrVal);
ShowMessage('loop='+ANode.ChildNodes[I].Attributes['UID']);
if Assigned(Result) then
begin
ShowMessage('found in loop='+ANode.ChildNodes[I].Attributes['UID']);
Exit;
end;
end;
end;
end;
end;
var
XML:IXMLDocument;
mnode,cnode:IXMLNode;
begin
XML:= NewXMLDocument;
XML.LoadFromFile('C:\test.xml');
mnode:=XML.DocumentElement;
//This will replace all Format="1" to Format="0"
cnode:=RecursiveFindNodeAttr(mnode,'TestNodeName','Format','1');
while cnode<>nil do
begin
cnode.Attributes['Format'] := '0';
ShowMessage(cnode.Attributes['UID']);
cnode:=RecursiveFindNodeAttr(mnode,'TestNodeName','Format','1');
end;
if cnode= nil then
ShowMessage('cnode= nil ');
end.
Upvotes: 0