2012-06-05 24 views
6

Ho un XML nel seguente formato:Lettura valori distinti da XML utilizzando XPath

<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> 

Come faccio a prelevare i numeri di conto unici utilizzando XPath? vale a dire, voglio ottenere i valori 1, 225 e 236.

Questo è quello che ho fatto: (sto usando 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; 

Ma questo non ha prodotto alcun nodi (accountdetails. lunghezza = 0). Per favore fatemi sapere cosa mi manca qui.

Grazie,

Pradeep

+4

Questa parte è parte del tuo codice reale? Guardandolo, non verrà nemmeno compilato. – TLama

+0

L'ho copiato; ma non ha corretto gli apostrofi. Volevo solo dare un'idea di cosa ho fatto ... – Pradeep

+0

Rispondere perché stai ricevendo la lista di lunghezza zero di 'IXMLDOMNodeList' è semplice. È perché hai provato a risolvere l'errore relativo ai caratteri '::' non validi in 'previous-sibling ::' sostituendolo con '/' ma questo significherebbe che stai confrontando il valore con il nodo 'precedente-fratello/.. 'Cosa c'è che non va. È necessario utilizzare 'previous-sibling' come parametro (usando' :: ') restituendo il nodo precedente, non come valore di nodo costante. Per utilizzare l'asse XPath è necessario utilizzare la versione più recente di MSXML poiché quella precedente non lo supporta. – TLama

risposta

5

Sembra che la versione di MSXML di Delphi 2007 non supporti l'asse XPath. Quindi, se si decide di utilizzare il seguente codice, l'importazione sia Microsoft XML, v3.0 o la libreria Microsoft XML, v6.0 tipo e poi provare questo:

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

Ho installato Microsoft xml 6.0, ma ottengo comunque un'eccezione Debugger. (Ora mi interessa conoscere il mehtod) – Zeina

+0

@Zeina, non mi hai detto tanto, che eccezione e quando? Intendi quando i nodi vengono selezionati dallo XPath? Ho provato il codice da questo post in Delphi 2007 su Windows 7 e Windows XP e funziona bene per me. – TLama

+0

questo è il messaggio: Eccezione classe EOleException con messaggio 'Expected token') 'found': ' ./Accounts/Account[not(@Number=preceding-sibling-->:<--:Account/@Number)] /@Numero'. L'XML Misrosoft non è installato correttamente? – Zeina

0

provare questo codice:

XMLDocument1: TXMLDocument; 

procedure TForm1.Button2Click(Sender: TObject); 
var 
    BodyNode:IXMLNode; 
    I: Integer; 
    sl:TStringList; 
begin 
    sl:=TStringList.Create; 
    XMLDocument1.Active:=False; 
    XMLDocument1.FileName:='C:\Zeina\Test.xml'; 
    XMLDocument1.Active:=True; 

    BodyNode:=XMLDocument1.DocumentElement; 
    for I:=0 to BodyNode.ChildNodes.Count-1 do 
    begin 
    if sl.IndexOf(BodyNode.ChildNodes[i].Attributes['Number'])=-1 then 
     sl.Add(BodyNode.ChildNodes[i].Attributes['Number']); 
    end; 
    Memo1.Lines:=sl; 
end; 

questo è il risultato:

+2

Funziona comunque, ma non è il modo XPath, ciò che è stato chiesto qui. – TLama

-1

Ziena, ho modificare il codice:

XMLDocument1: TXMLDocument; 

procedure TForm1.Button2Click(Sender: TObject); 
var 
    BodyNode:IXMLNode; 
    I: Integer; 
    sl:TStringList; 
begin 
    sl:=TStringList.Create; 
    XMLDocument1.Active:=False; 
    XMLDocument1.FileName:='C:\Zeina\Test.xml'; 
    XMLDocument1.Active:=True; 

    BodyNode:=XMLDocument1.DocumentElement; 
    sl.Duplicates := dupIgnore; 
    for I:=0 to BodyNode.ChildNodes.Count-1 do 
    sl.Add(
     BodyNode.ChildNodes[i].Attributes['Number']); // ignore duplicated lines 
    Memo1.Lines.AddStrings(sl); // for avoid memory leak 
end; 
+2

E l'XPath? – TLama

1

Non capisco esattamente cosa si vuole raggiungere. Forse questo?

'/Accounts/Account[not(@Number=preceding-sibling::node()/@Number)]/@Number' 
+0

Questo fallirà con la vecchia versione della libreria di tipi MSXML fornita con Delphi 2007 con l'errore 'Eccezione classe EOleException con messaggio 'Expected token') 'found': './Account/Account [non (@ numero = precedente-fratello ->: <-: nodo()/@ numero)] '. ' – TLama

+0

@TLama Rispettare la nuova libreria di tipi, come suggerito nella risposta . Non mi è chiaro che la domanda riguardi l'uso di xpath e gli assi in delphi 2007, o circa l'espressione xpath da usare? o forse entrambi? – balazs

+0

Stavo cercando una soluzione usando l'espressione xpath che funziona nella mia versione di Delphi ... – Pradeep

1

Questo funziona nel mio XPath example in Delphi XE2 e un Delphi 2007 update of the XPath example dà questo output:

nodeName[0]:Number 
nodeValue[0]:1 
nodeName[1]:Number 
nodeValue[1]:225 
nodeName[2]:Number 
nodeValue[2]:236 

Le idee nell'esempio dovrebbe farti andare con Delphi e il 2007 utilizzando il MSXML 6 DOM, o l'OpenXML DOM (Delphi XE2 supporta MSXML 6 DOM o ADOM XML v4 DOM).

Si noti che il comportamento di MSXML 6 dipende in gran parte dalla versione effettiva installata che dipende dal sistema operativo Windows (da qui this answer).

Il che probabilmente significa che non si dispone dell'ultimo MS XML 6 installato, o che non è stato importato il giusto numero di librerie (l'unità msxml di Delphi 2007 non contiene IXMLDOMDocument2 di cui si ha bisogno per il supporto XPath).

Il 27 luglio 2012, ho dedicato un po 'di tempo per riprendere l'esempio di Delphi 2007 e testarlo.
Non sono completamente sicuro che sia completamente privo di memoria (ho hackerato una classe TDictionary per inserire le interfacce), ma i risultati sono gli stessi dell'esempio Delphi XE2 e a prima vista sembra abbastanza OK.

Nell'app demo XPathTester, caricare l'Esempio 3, quindi eseguire l'XPath (che caricherà il proprio esempio ed eseguirà l'XPath).

Si noti che la maggior parte delle altre cose nello bo library richiede almeno Delphi 2009, ma questa parte funziona.
Nelle prossime settimane lo testerò come richiesto in un altro progetto che è ancora in Delphi 2007.

procedure TMainForm.LoadXmlExample3ButtonClick(Sender: TObject); 
begin 
    LoadXmlExample([ // unique account numbers 
    '/Accounts/Account[not(@Number=preceding-sibling::Account/@Number)]/@Number' 
    ], 
    [ 
    '<?xml version="1.0"?>', 
    '<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>' 
    ]); 
end; 
+0

Grazie per la soluzione, ma questo non funzionerà nella mia versione di Delphi .. – Pradeep

+0

Attualmente la mia risposta è di dimostrare che la query XPath funziona sull'XML usando MSXML 6. Quindi sì: dovrebbe essere possibile farlo anche in Delphi 2007. Se/quando trovo il tempo di tradurre il progetto di esempio in Delphi 2007, ti farò sapere da una modifica a questa risposta. –

+0

@ user466744 Finalmente ho avuto la possibilità di lavorare sul porting della demo XPath do Delphi 2007. Si prega di vedere se è possibile eseguire il codice che è memorizzato qui: http://bo.codeplex.com/SourceControl/changeset/80654 –