Search code examples
delphidelphi-2010delphi-2007

Reading distinct values from XML using XPath


I have an XML in the following format:

<Accounts>
   <Account Number="1"   DebitAmount="1000" Amount="2827561.95" /> 
   <Account Number="225" DebitAmount="2000"  Amount="12312.00" /> 
   <Account Number="236" DebitAmount="London"    Amount="457656.00" /> 
   <Account Number="225" DebitAmount="London"    Amount="23462.40" /> 
   <Account Number="236" DebitAmount="Bangalore" Amount="2345345.00" /> 
</Accounts>

How do I retreive the unique Account Numbers using Xpath? ie, I want to get the values 1, 225 and 236.

This is what I did:(I'm using Delphi 2007...)

Const XmlStr =
' <Accounts>
   <Account Number="1"   DebitAmount="1000" Amount="2827561.95" /> 
   <Account Number="225" DebitAmount="2000"  Amount="12312.00" /> 
   <Account Number="236" DebitAmount="London"    Amount="457656.00" /> 
   <Account Number="225" DebitAmount="London"    Amount="23462.40" /> 
   <Account Number="236" DebitAmount="Bangalore" Amount="2345345.00" /> 
</Accounts>';

 function GetAccountNumbers:TList;
 Var
   XMLDOMDocument  : IXMLDOMDocument;
   accounts : IXMLDOMNodeList;
  accountdetail :IXMLDOMNode;
   i:Integer
   list :TList
 begin
   Result:=TList.Create;
   XMLDOMDocument:=CoDOMDocument.Create;
   XMLDOMDocument.loadXML(XmlStr);
   accounts:= XMLDOMDocument.SelectNodes(''./Accounts 
  /Account[not(@Number=preceding-sibling/ Account /@Number)]');
  for i := 0 to accountdetails.length - 1 do begin
     accountdetail := accountdetails.item[i];
     //omitting the "<>nil" checks...
     list.Add(accountdetail.attributes.getNamedItem('Number').Nodevalue;
  end;
 end;

But this returns no nodes(accountdetails.length=0). Please let me know what I am missing here.

Thanks,

Pradeep


Solution

  • It seems the version of the MSXML from Delphi 2007 doesn't support XPath axis. So if you decide to use the following code, import either Microsoft XML, v3.0 or the Microsoft XML, v6.0 type library first and then try this:

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, MSXML2_TLB;
    
    type
      TForm1 = class(TForm)
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    const
      XMLString =
        '<Accounts>' +
        '<Account Number="1" DebitAmount="1000" Amount="2827561.95"/>' +
        '<Account Number="225" DebitAmount="2000"  Amount="12312.00"/>' +
        '<Account Number="236" DebitAmount="London" Amount="457656.00"/>' +
        '<Account Number="225" DebitAmount="London" Amount="23462.40"/>' +
        '<Account Number="236" DebitAmount="Bangalore" Amount="2345345.00"/>' +
        '</Accounts>';
    
    type
      TIntegerArray = array of Integer;
    
    function GetAccountNumbers(const AXMLString: string): TIntegerArray;
    var
      I: Integer;
      XMLDOMNodeList: IXMLDOMNodeList;
      XMLDOMDocument: IXMLDOMDocument3;
    begin
      XMLDOMDocument := CoDOMDocument60.Create;
      if Assigned(XMLDOMDocument) and XMLDOMDocument.loadXML(AXMLString) then
      begin
        XMLDOMNodeList := XMLDOMDocument.selectNodes('/Accounts/Account[not(@Number=preceding-sibling::Account/@Number)]/@Number');
        SetLength(Result, XMLDOMNodeList.length);
        for I := 0 to XMLDOMNodeList.length - 1 do
          Result[I] := XMLDOMNodeList.item[I].nodeValue;
      end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      S: string;
      I: Integer;
      IntegerArray: TIntegerArray;
    begin
      S := 'Account numbers: ';
      IntegerArray := GetAccountNumbers(XMLString);
      for I := 0 to Length(IntegerArray) - 1 do
        S := S + IntToStr(IntegerArray[I]) + ', ';
      Delete(S, Length(S) - 1, 2);
      ShowMessage(S);
    end;
    
    end.