0
votes

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;
2
"It gives wrong results" is no use to us. You'll need to provide the input XML, the parameters you pass to the function, your expected result, and the actual result. Remember that we cannot see your screen. Please edit the question to supply the missing detail. - David Heffernan
It gives wrong results when i call it second time in program. It returns node even attribute value is not "1" (check example of call above), but it should return "nil". - Nafalem
Please include in the question, not in comments, all the information I described. Learning how to ask a question correctly will take you a long way towards being self-sufficient. - David Heffernan
@Nafalem I edited your question to make it clearer but you should still edit it and add the XML. - Jan Doggen
@Nafalem "XPath is unknown term for me" is wrong phrase. Correct phrase "XPath is a new opportunity to learn effective practice and become more knowledgable" Also "i do not know how to use it" is wrong phrase, correct one is "i already read Wikipedia article about XPAth and now i am Googling for XPath tutorials and XPath online testing sites" - Arioch 'The

2 Answers

1
votes

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.
0
votes

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.