0

I have some perl code

my $res = $ua->get( $access->to_url );
if ($res->is_success) {
my $ref = XMLin( $res->content );


    my $xml = new XML::Simple;
    $data = $xml->XMLin($res->content,ForceArray => 1);
    #print $res->content;
    for my $purchase ( @{ $data->{PurchaseOrders}->{PurchaseOrder}} )

This bit is fine....

How ever when i try loop through child elements, if there is only one child element

i get the "not an array reference" error

for my $item ( @{$purchase->{LineItems}->{LineItem}} ) 
    {
    $itemCode = $item->{ItemCode}; 
    }

The XML structure is something like this

PurchaseOrders

    PurchaseOrder

        LineItems

            LineItem

i am aware of an issue with xml simple where i have to forceArray, but i am not sure how to forceArray on the child Nodes

I found this article on stackoverflow that seems very close to my exact problem, but i am struggling on how to execute it with in my code

perl, parsing XML using XML::Simple

   $VAR1 = {
      'PurchaseOrderID' => '82fa50d6-fd45-4fd2-b42d-035aaaa39a2c',
      'LineAmountTypes' => 'Exclusive',
      'SentToContact' => 'true',
      'AttentionTo' => 'sxxxx',
      'Status' => 'AUTHORISED',
      'LineItems' => {
                       'LineItem' => {
                                       'LineAmount' => '57.61',
                                       'Quantity' => '1.0000',
                                       'UnitAmount' => '57.6100',
                                       'LineItemID' => 'e295d55d-68bd',
                                       'Description' => 'xxx',
                                       'ItemCode' => 'xxx',
                                       'TaxAmount' => '11.52',
                                       'AccountCode' => '310',
                                       'TaxType' => 'INPUT2'
                                     }
                     },
      'UpdatedDateUTC' => '2018-10-26T14:19:19.053',
      'CurrencyCode' => 'GBP',
      'Contact' => {

Included a snipet from my print dumper - please note,its just a snipet of the important part, everything is fine until it hits line items

Also here is the XML file

<PurchaseOrder>
  <PurchaseOrderID>82fa50</PurchaseOrderID>
  <PurchaseOrderNumber>PO-0029</PurchaseOrderNumber>
  <Date>2018-10-26T00:00:00</Date>
  <DeliveryDate>2018-10-28T00:00:00</DeliveryDate>
  <DeliveryAddress>Address/DeliveryAddress>
  <AttentionTo>XXX</AttentionTo>
  <SentToContact>true</SentToContact>
  <Reference>000000078</Reference>
  <CurrencyRate>1.000000</CurrencyRate>
  <CurrencyCode>GBP</CurrencyCode>
  <Contact>
    <ContactID>f203ed00-8cd1-4e4d-9b76-f5e7d90a3c19</ContactID>
    <ContactStatus>ACTIVE</ContactStatus>
    <Name>XXX</Name>
    <FirstName>XXXy</FirstName>
    <LastName>XXX</LastName>
    <Addresses>
      <Address>
        <AddressType>XXX</AddressType>
        <AddressLine1>XXX</AddressLine1>
        <AddressLine2>XXX</AddressLine2>
        <City>XXX</City>
        <Region>XXX</Region>
        <PostalCode>XXX</PostalCode>
        <Country>GBR</Country>
      </Address>
      <Address>
        <AddressType>XXX</AddressType>
        <AddressLine1>Unit 1-3</AddressLine1>
        <AddressLine2>XXX</AddressLine2>
        <City>XXX</City>
        <Region>West Yorkshire</Region>
        <PostalCode>POSTCODE</PostalCode>
        <Country>GBR</Country>
      </Address>
    </Addresses>
    <UpdatedDateUTC>2018-10-08T17:19:55.083</UpdatedDateUTC>
    <DefaultCurrency>GBP</DefaultCurrency>
  </Contact>
  <BrandingThemeID>2ffe566f-7a88-486a-938c-639d27966197</BrandingThemeID>
  <Status>AUTHORISED</Status>
  <LineAmountTypes>Exclusive</LineAmountTypes>
  <LineItems>
    <LineItem>
      <ItemCode>xxx</ItemCode>
      <Description>des</Description>
      <UnitAmount>57.6100</UnitAmount>
      <TaxType>INPUT2</TaxType>
      <TaxAmount>11.52</TaxAmount>
      <LineAmount>57.61</LineAmount>
      <AccountCode>310</AccountCode>
      <Quantity>1.0000</Quantity>
      <LineItemID>e295d55d-68bd-41b0-a0b1-cf1f2d5b7a4f</LineItemID>
    </LineItem>
  </LineItems>
  <SubTotal>57.61</SubTotal>
  <TotalTax>11.52</TotalTax>
  <Total>69.13</Total>
  <UpdatedDateUTC>2018-10-26T14:19:19.053</UpdatedDateUTC>
  <HasAttachments>false</HasAttachments>
</PurchaseOrder>
Fabio
  • 49
  • 6
  • 1
    Please show us the output when using `Data::Dumper`. Make that output short but relevant please. You are already using `ForceArray`, which means that all your structures should be arrays, but it seems that something is wrong somewhere in the data. – Corion Oct 26 '18 at 17:58
  • 1
    What Perl version are you using? Wondering if you are accidentally using pseudohashes (an old removed misfeature) and missing a needed `->[0]` before one of your hash dereferences – ysth Oct 26 '18 at 18:05
  • `$purchase->{LineItems}->{LineItem}` might be undefined for some items – UjinT34 Oct 26 '18 at 18:35
  • 1
    See [Why XML::Simple is Discouraged?](https://stackoverflow.com/q/33267765/589924) – ikegami Oct 26 '18 at 18:36
  • added a snipet of my print dumper, sorry should of posted this orignally – Fabio Oct 26 '18 at 19:37

1 Answers1

1

You can avoid issues with ForceArray and confusing data structures by using an XML parser that returns an object that understands the XML tree. Mojo::DOM is a nice one if you know CSS.

use Mojo::DOM;

my $dom = Mojo::DOM->new->xml(1)->parse($res->decoded_content);
for my $purchase ($dom->find('PurchaseOrders > PurchaseOrder')->each) {
  # $purchase is a Mojo::DOM object representing a PurchaseOrder element
  for my $item ($purchase->find('LineItems > LineItem')->each) {
    # It's unclear if ItemCode is an an attribute or a sub-element; assuming sub-element
    my $itemCode = $item->at('ItemCode')->text;
    ...
  }
}

XML::LibXML is another option that can be used similarly but using XPath or DOM instead of CSS to locate elements.

use XML::LibXML qw( );

my $doc = XML::LibXML->load_xml(string => $res->decoded_content);
for my $purchase ($doc->findodes('/PurchaseOrders/PurchaseOrder')) {
  # $purchase is a XML::LibXML::Element object representing a PurchaseOrder element
  for my $item ($purchase->findnodes('LineItems/LineItem')) {
    # It's unclear if ItemCode is an an attribute or a sub-element; assuming sub-element
    my $itemCode = $item->findvalue('ItemCode');
    ...
  }
}
Grinnz
  • 9,093
  • 11
  • 18
  • 1
    Hi Thank you Sadly i have so much code written with the XML SIMPLE and this is the only last problem i have to solve and really want to avoid a re-write – Fabio Oct 26 '18 at 22:15
  • @ikegami I'm not sure the purpose of the last edit, XML::LibXML takes XPath queries rather than CSS queries, DOM traversal works the same way in both libraries. – Grinnz Oct 26 '18 at 23:01
  • Mojo::DOM, despite its name, doesn't follow the DOM spec. XML::LibXML, on the other hand, does implement (part of) the DOM API. Someone wishing to use that instead of CSS selectors or XPath selectors could use XML::LibXML – ikegami Oct 26 '18 at 23:12
  • @ikegami if I understand what you're referring to correctly, Mojo::DOM provides a traversal API with the same capabilities, just not following the spec that XML::LibXML does in regards to object types. Thus it's a bit misleading to refer to it as a unique feature, that's all. – Grinnz Oct 26 '18 at 23:21
  • Providing a known, standard interface (e.g. CSS, XPath or DOM) is a feature. That's why you bothered to mention it. I just added the one that was missing. – ikegami Oct 26 '18 at 23:23
  • Forgot to mention, but you are free to revert, especially since it was a substantive change. (Btw, I only removed strict/warnings to limit the height of the answer. Including them in both snippets would have added 6 lines with two snippets. It was a comment on the inclusion of strict/warnings pragmas in general.) – ikegami Oct 27 '18 at 02:40