0

XML file:

<Partner>
...
</Partner>
<Partner>
  <K1>10</K1>
  <K2>3</K2>
  <K3>5254304</K3>
  <K4>test name</K4>
  <K5>637.51</K5>
  <K6>159.38</K6>
  <K7>802.39</K7>
  <K8>0.00</K8>
  <K9>802.39</K9>
  <Invoices>
    <Invoice>
      <R1>1</R1>
      <R2>4-02R0113-12</R2>
      <R3>2014-12-29</R3>
      <R4>2014-12-29</R4>
      <R5>398</R5>
      <R6>637.51</R6>
      <R7>159.38</R7>
      <R8>802.39</R8>
      <R9>0.00</R9>
      <R10>802.39</R10>
    </Invoice>
  </Invoices>
</Partner>
<Partner>
...
</Partner>

In my XML file I have repeating nodes <Partner>. Every partner has its own identification number written in node <K3>.

Every partner can have multiple invoices.

I need to find and read values in <R6> and <R7> in the invoice where I know values for <R2>, <R3>, <R8>.

How do I search for specific Invoice where search criteria are multiple fields <R2>, <R3>, <R8> of partner where search criteria is field <K3> and get field values for <R6> and <R7>?

How to add multi criteria to SelectSingleNode?

My code:

procedure TfrmTest.TestReadOmniXML;
var
  xml: IXMLDocument;
  iNodePartner, iNodePartnerInvoice, iNodePartnerInvoiceR6, iNodePartnerInvoiceR7 : IXMLNode;
begin
  xml := CreateXMLDoc;
  xml.Load('c:\test.xml');
  iNodePartner := XML.SelectSingleNode('//Partner[K3=' + '0254304' + ']');
  iNodePartnerInvoice := iNodePartner.SelectSingleNode(
  //single query works OK
  './/Racuni/Racun[R2=' + '4-02R0113-12' + ']' 

  //but I need to add these fields also
//    ' and [R3=' + '2014-12-29' + ']' +
//    ' and [R8=' + '802.39' + ']'     
  );

  if Assigned( iNodePartnerInvoice ) then
  begin
    iNodePartnerInvoiceR6 := iNodePartnerInvoice.SelectSingleNode('./R6');
    Label1.Caption := iNodePartnerInvoiceR6.Text;
    iNodePartnerInvoiceR7 := iNodePartnerInvoice.SelectSingleNode('./R7');
    Label2.Caption := iNodePartnerInvoiceR7.Text;
  end;

...

end;
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
joskona
  • 13
  • 1
  • 6
  • IIRC, this works: './/Racuni/Racun[R2=' + '4-02R0113-12' + ']' + ' [R3=' + '2014-12-29' + ']' + '[R8=' + '802.39' + ']' – gabr Feb 02 '16 at 15:25
  • I tried it just now. For me it doesn't work. The node is nil. – joskona Feb 02 '16 at 16:06

3 Answers3

0

Given the example above, you can do something along the lines of:

//Partner[K2=3]/Invoices/Invoice[R1=1 and R5=398]

This gets the Ivoices of partner with K2=3 where ivoices have specific values for r1 and r5. Then, depending on how you need to process the invoices that match these criteria (e.g. if there is more than one invoice) you can then use an XPath expression (like sum or something else) to return a single value calculated from these Invoice elements.

anakic
  • 2,746
  • 1
  • 30
  • 32
  • Wrote this iNodepartner := FXMLDocument.SelectSingleNode('//Partner[K3=0254304]/Invoices/Invoice[R2=4-02R0113-12 and R8=802.39]'); Node is nill so still doesn't work. – joskona Feb 02 '16 at 18:34
  • I think you need to include quotes for the value of R2 in the xpath query (in general for all string values). I tried this and it worked fine on the example XML in the question (i.e. it found the invoice): //Partner[K3=5254304]/Invoices/Invoice[R2='4-02R0113-12' and R8=802.39] I tested here: http://www.freeformatter.com/xpath-tester.html#ad-output – anakic Feb 02 '16 at 19:18
  • OmniXML doesn't support 'and'. – gabr Feb 03 '16 at 08:30
0

Accordingly OmniXMLXPath.pas file, OmniXML does not support logical operation in XPath. So you can't search by 2+ attributes simultaneously.

You can select multiple nodes by calling SelectNodes('//Partner[K3=5254304]/Invoices/Invoice[R2='4-02R0113-12']) and check nodes by another attributes.

Or use MSXML parser (OmniXML_MSXML unit) if you can. This will work on your demo data:

procedure TForm1.Button1Click(Sender: TObject);
var
  xml: IXMLDocument;
  iNodePartner: IXMLNode;
begin
  xml := CreateXMLDoc;
  xml.Load('c:\test.xml');
  iNodePartner := XML.selectSingleNode('//Partner[K3=5254304]/Invoices/Invoice[R2=''4-02R0113-12'' and R8=''802.39'']');
  if iNodePartner = nil then
    MessageDlg('Not found',mterror,[mbok],0);
end;
  • Thanks but this doesn't work iNodePartner := XML.selectSingleNode('//Partner[K3=5254304]/Invoices/Invoice[R2=''4-02R0113-12'' and R8=''802.39'']'); If I place it like this node does exists iNodePartner := XML.selectSingleNode('//Partner[K3=5254304]/Invoices/Invoice[R2=''4-02R0113-12'']'); but as soon as you add another query it doesn't work iNodePartner := XML.selectSingleNode('//Partner[K3=5254304]/Invoices/Invoice[R2=''4-02R0113-12'' and R8=''802.39'']'); – joskona Feb 02 '16 at 21:57
  • What is not work? The code above verified on your sample xml data. – Aleksey Kharlanov Feb 02 '16 at 22:04
  • @joskona, my sample code will ONLY work with MSXML parser, you must replace OmniXML unit in uses clause to OmniXML_MSXML unit! OmniXML itself doesn't not support 'AND' operator in XPath. – Aleksey Kharlanov Feb 03 '16 at 06:38
0

OmniXMLXPath doesn't support that out of the box. I have, however, added support for consecutive filters last year, but forgot to push that change to the public site :(

With the updated version, this will work:

program Project29;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  OmniXML,
  OmniXMLXpath,
  OmniXMLUtils;

var
  xml: IXMLDocument;
  node: IXMLNode;

begin
  xml := CreateXMLDoc;
  if XMLLoadFromFile(xml, 'c:\0\partner.xml') then begin
    node := xml.SelectSingleNode('//Partner/Invoices/Invoice[R2="4-02R0113-12"][R3="2014-12-29"][R8="802.39"]');
    if not assigned(node) then
      Writeln('not found')
    else begin
      Writeln('R6=', GetNodeTextStr(node, 'R6', ''));
      Writeln('R7=', GetNodeTextStr(node, 'R7', ''));
    end;
  end;
  Readln;
end.

For now, you can get the fresh OmniXMLXPath.pas here: https://www.dropbox.com/s/nnvrz6wnmnpmxzn/OmniXMLXPath.pas

gabr
  • 26,580
  • 9
  • 75
  • 141