Delphi - Iterating through an XML dataset with NativeXML

3.7k Views Asked by At

I'm trying to migrate some xml code from the default delphi XML routines to NativeXML, hopefully to improve the speed (a lot).

The XML files are of the form:

<Datafile>
  <Header>
    <Name>'My Name'</Name>
    <Address>'My Address'</Address>
  </Header>
  <Body>
    <ValuesSets>
      <ValuesSet>
        <v>1</v>
        <v>2</v>
        <v>3</v>
        <v>4</v>
      </ValuesSet>
      <ValuesSet>
        <v>5</v>
        <v>6</v>
        <v>7</v>
        <v>8</v>
      </ValuesSet>
    </ValuesSets>
  </Body>
</Datafile>

My problem is how to iterate through each of the sets of values. A more or less direct translation of the old code didn't work:

procedure TForm1.Button1Click(Sender: TObject);

var
    AXMLDoc       : TNativeXML ;
    FileID        : TFilename ;
    I             : Integer ;
    J             : Integer ;
    HeaderNode    : TXMLNode ;
    BodyNode      : TXMLNode ;
    ValuesSetsNode : TXMLNode ;
    ValuesSetNode  : TXMLNode ;

    Values         : array of array of integer ;

begin
try
    Memo1.Lines.Clear ;
    FileID := 'Sample.XML' ;
    Memo1.Lines.LoadFromFile (FileID) ;
    AXMLDoc := TNativeXml.Create (nil) ;
    AXMLDoc.LoadFromFile (FileID) ;

    if Assigned(AXMLDoc.Root) then
        begin
        HeaderNode := AXMLDoc.Root.NodeByName ('Header') ;
        if Assigned (HeaderNode) then
            begin
            //  < process header items >
            BodyNode := AXMLDoc .Root.NodeByName ('Body') ;
            if Assigned (BodyNode) then
                begin
                ValuesSetsNode := BodyNode.NodeByName ('ValuesSets') ;
                if Assigned (ValuesSetsNode) then
                    begin
                    SetLength (Values, ValuesSetsNode.NodeCount) ;
                    for i := 0 to ValuesSetsNode.NodeCount - 1 do
                        begin
                        ValuesSetNode := ValuesSetsNode [i] ;
                        if Assigned (ValuesSetNode) then
                            begin
                            SetLength (Values [i], ValuesSetNode.NodeCount) ;
                            for j := 0 to ValuesSetNode.NodeCount - 1 do
                                begin
                                Values [i, j] := StrToIntDef (ValuesSetNode [j].Value, 0) ;
                                end ;
                            end ;
                       end ;
                    end ;
                end ;
            end ;
        end ;

    for i := 0 to Length (Values) - 1 do
        begin
        for j := 0 to Length (Values [i]) - 1 do
            begin
            Memo1.Lines.Add (Format ('Values [%d, %d] = %d', [i, j, Values [i, j]])) ;
            end ;
       end ;

finally
    FreeAndNil (AXMLDoc) ;
    end ;
end ;

The output I get is:

Values [1, 0] = 0
Values [1, 1] = 1
Values [1, 2] = 0
Values [1, 3] = 2
Values [1, 4] = 0
Values [1, 5] = 3
Values [1, 6] = 0
Values [1, 7] = 4
Values [1, 8] = 0
Values [3, 0] = 0
Values [3, 1] = 5
Values [3, 2] = 0
Values [3, 3] = 6
Values [3, 4] = 0
Values [3, 5] = 7
Values [3, 6] = 0
Values [3, 7] = 8
Values [3, 8] = 0

and I was expecting:

Values [0, 0] = 1
Values [0, 1] = 2
Values [0, 2] = 3
Values [0, 3] = 4
Values [1, 0] = 5
Values [1, 1] = 6
Values [1, 2] = 7
Values [1, 3] = 8

so it seems as if the Nodes property of TNativeXML is not exactly the same as IXMLNode's ChildNodes property.

How do I iterate all the child nodes within a parent node? I don't want to give each one a unique name (<v1001>1234</v1001>, <v1002>4321</v1002>... etc), as I only ever need to access them sequentially, and don't want the speed penalty (or increased file size) of having to do a NodeByName for every value (there can be many of these values).

UPDATE **

NativeXML does have an equivalent to ChildNodes - it's called Containers (not ChildContainers as the online documentation would have you believe). The following worked:

var
    AXMLDoc       : TNativeXML ;
    FileID        : TFilename ;
    I             : Integer ;
    J             : Integer ;
    HeaderNode    : TXMLNode ;
    BodyNode      : TXMLNode ;
    ValuesSetsNode : TXMLNode ;
    ValuesSetNode  : TXMLNode ;

    Values         : array of array of integer ;

begin
try
    Memo1.Lines.Clear ;
    FileID := 'Sample.XML' ;
    Memo1.Lines.LoadFromFile (FileID) ;
    AXMLDoc := TNativeXml.Create (nil) ;
    AXMLDoc.LoadFromFile (FileID) ;

    if Assigned(AXMLDoc.Root) then
        begin
        HeaderNode := AXMLDoc.Root.NodeByName ('Header') ;
        if Assigned (HeaderNode) then
            begin
            //  < process header items >
            BodyNode := AXMLDoc .Root.NodeByName ('Body') ;
            if Assigned (BodyNode) then
                begin
                ValuesSetsNode := BodyNode.NodeByName ('ValuesSets') ;
                if Assigned (ValuesSetsNode) then
                    begin
                    SetLength (Values, ValuesSetsNode.ContainerCount) ;
                    for i := 0 to ValuesSetsNode.ContainerCount - 1 do
                        begin
                        ValuesSetNode := ValuesSetsNode.Containers [i] ;
                        if Assigned (ValuesSetNode) then
                            begin
                            SetLength (Values [i], ValuesSetNode.ContainerCount) ;
                            for j := 0 to ValuesSetNode.ContainerCount - 1 do
                                begin
                                Values [i, j] := StrToIntDef (ValuesSetNode.Containers [j].Value, 0) ;
                                end ;
                            end ;
                       end ;
                    end ;
                end ;
            end ;
        end ;

    for i := 0 to Length (Values) - 1 do
        begin
        for j := 0 to Length (Values [i]) - 1 do
            begin
            Memo1.Lines.Add (Format ('Values [%d, %d] = %d', [i, j, Values [i, j]])) ;
            end ;
       end ;

finally
    FreeAndNil (AXMLDoc) ;
    end ;
end ;

It's actually pretty slow - to read 32k float values takes many 10's of seconds.

1

There are 1 best solutions below

1
On

OP :

so it seems as if the Nodes property of TNativeXML is not exactly the same as IXMLNode's ChildNodes property.

You are right . There must be something more to be done to achieve this result.

enter image description here

procedure TForm1.Button1Click(Sender: TObject);
var
[...]
    i , i2        : Integer ;
    j , j2        : Integer ;
[...]
begin
try
    Memo1.Lines.Clear ;
    [...]
            BodyNode := AXMLDoc .Root.NodeByName ('Body') ;
             if Assigned (BodyNode) then
             begin
                 ValuesSetsNode := BodyNode.NodeByName ('ValuesSets') ;
                 if Assigned (ValuesSetsNode) then
                 begin
                     SetLength (Values, ValuesSetsNode.NodeCount) ;
                     ValuesSetNode := ValuesSetsNode.NodeByName('ValuesSet') ;
                     if Assigned (ValuesSetNode) then
                     begin
                       i2:=0;
                        for i := 0 to ValuesSetSNode.NodeCount - 1 do begin
                         if i > 0 then ValuesSetNode := ValuesSetsNode.NextSibling(ValuesSetNode) ;
                         if ValuesSetNode=nil then break;
                         if ValuesSetNode.NodeCount > 0 then begin
                            SetLength(Values[i2], ValuesSetNode.NodeCount) ;
                            j2:=0;
                               for j := 0 to ValuesSetNode.NodeCount - 1 do begin
                                if pos(#13,ValuesSetNode[j].Value) > 0 then continue;
                                Values [i2, j2] := StrToIntDef (ValuesSetNode[j].Value, 0) ;
                                inc(j2);
                               end ; // for j
                           SetLength(Values[i2],j2);
                           inc(i2);
                         end;
                        end ; // for i
                     end;     // ValuesSetNode
                 end;         // ValuesSetsNode
             end;             // BodyNode
        end;                  // HeaderNode
    end;                      // AXMLDoc.Root

[...] 

finally
    FreeAndNil (AXMLDoc) ;
    end ;
end ;

Delphi 5 / Delphi XE2 NativeXml 4.07