You are here:  » problem with looping


problem with looping

Submitted by hoeksms on Wed, 2010-03-24 10:25 in

I've used MagicParser for een few years now and I've never had any problems. Until today. The XML that I'm trying to parse describes books (ONIX) and some books have multiple authors.

The simplified XML looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<ONIXMessage>
<Product>
<bookTitle>Book nr 1</bookTitle>
<Contributor id="101">
<ContributorRole>author</ContributorRole>
<NamesBeforeKey>John</NamesBeforeKey>
<KeyNames>Smith</KeyNames>
</Contributor>
<Contributor id="102">
<ContributorRole>author</ContributorRole>
<NamesBeforeKey>Peter</NamesBeforeKey>
<KeyNames>Johnson</KeyNames>
</Contributor>
</Product>
<Product>
<bookTitle>Book nr 2</bookTitle>
<Contributor id="0">
<ContributorRole>author</ContributorRole>
<UnnamedPersons>03</UnnamedPersons>
</Contributor>
<Contributor id="103">
<ContributorRole>author</ContributorRole>
<NamesBeforeKey>Mark</NamesBeforeKey>
<KeyNames>Peterson</KeyNames>
</Contributor>
</Product>
</ONIXMessage>
</xml>

As you can see in the second record some books have so many authors that only one is mentioned and the second author is 'unnamed'. The problem is that in that case the fields NamesBeforeKey and KeyNames are not just empty, but they are missing.

When I loop through the 'contributors', the data 'shifts'. The output looks like this:


Book nr 1
101 - John Smith
102 - Peter Johnson

Book nr 2
0 - Mark Peterson
103 -

The contributor with ID=0 gets the name contributor nr 103 ('Mark Peterson') because this is the first instance of these fields and the contributor with ID=0 gets no name at all.

When I test this XML in the demo of your site, the problem becomes clear (see this screenshot). My conclusion is dat this is default behaviour (?!?)

The code I'm using looks like this:

<?
require("includes/functions/MagicParser.php");
function myRecordHandler($product) {
echo $product["BOOKTITLE"] . '<br>';
$i = 0;
$postfix = "";
while(1) {
if ($i) $postfix = "@".$i;
if (!isset($product["CONTRIBUTOR".$postfix])) break;
$contributorID = $product["CONTRIBUTOR".$postfix."-ID"];
$contributorRole = $product["CONTRIBUTOR/CONTRIBUTORROLE".$postfix];
$contributorFirstName = $product["CONTRIBUTOR/NAMESBEFOREKEY".$postfix];
$contributorLastName = $product["CONTRIBUTOR/KEYNAMES".$postfix];
echo $contributorID . ' - ' . $contributorFirstName . ' ' . $contributorLastName . '<br>';
$i++;
}
}
MagicParser_parse($myxml,'myRecordHandler','xml|ONIXMESSAGE/PRODUCT/');
?>

Submitted by support on Wed, 2010-03-24 10:40

Hi,

Yes - I see what's happening. I would actually recommend a different looping approach in this instance, which is to loop through all records and identify the ones you are interested in using string comparison. Have a go with something like this:

<?php
require("includes/functions/MagicParser.php");
function 
myRecordHandler($product) {
echo 
$product["BOOKTITLE"] . '<br>';
foreach(
$product as $k => $v)
{
  if (
strpos($k,"CONTRIBUTOR") && strpos($k,"ID"))
  {
    
$contributorID $v;
    
// ID is always first, so reset all other fields
    
$contributorRole "";
    
$contributorUnnamedPersons "";
    
$contributorFirstName "";
    
$contributorLastName "";
  }
  if (
strpos($k,"CONTRIBUTOR") && strpos($k,"CONTRIBUTORROLE"))
  {
    
$contributorRole $v;
  }
  if (
strpos($k,"CONTRIBUTOR") && strpos($k,"UNNAMEDPERSONS"))
  {
    
$contributorUnnamedPersons $v;
  }
  if (
strpos($k,"CONTRIBUTOR") && strpos($k,"NAMESBEFOREKEY"))
  {
    
$contributorFirstName $v;
  }
  if (
strpos($k,"CONTRIBUTOR") && strpos($k,"KEYNAMES"))
  {
    
$contributorLastName $v;
  }
  
// now see if we have all values, and use / display as necessary
  
if (
     (
$contributorID && $contributorRole && $contributorFirstName && $contributorLastName)
     ||
     (
$contributorID && $contributorRole && $contributorUnnamedPersons)
     )
     {
       echo 
$contributorID ' - ' $contributorFirstName ' ' $contributorLastName '<br>';
     }
  }
}
MagicParser_parse($myxml,'myRecordHandler','xml|ONIXMESSAGE/PRODUCT/');
?>

Hope this helps!
Cheers,
David.

Submitted by hoeksms on Wed, 2010-03-24 11:49

David,

Thanx for your fast reply. I've tested your script and I had to make two minor changes:

'strpos($k,"CONTRIBUTOR")' returns 0 and is recognized as 'false' on my server.
I now use:

if (strpos($k,"CONTRIBUTOR") !== false) {
}

And I moved the resetting of the variables to the end of the loop (when the vars are complete).
In your example one author will be processed twice when the loop hits the next record ($k = CONTRIBUTOR@1)

With these minor changes, the script seems to work.
Thanx again.

Sybrand

Submitted by hoeksms on Thu, 2010-04-01 10:32

Although I've got the specific example (contributors) working with the workaround you suggested, I still have a question about this problem.

What if I have een nested loop with a few optional fields that are not in any way related to each other. So there's no way to predict when the loop is finished.
Below is an example with two mandatory fields (field1, field2) and 8 optional fields (field3-10). In this case there is no way to predict when the fields of the nested loop are 'complete'.

<xml>
<product>
    <productTitle>product nr 1</productTitle>
    <fieldset>
        <field1>data</field1>
        <field2>data</field2>
        <field5>data</field5>
        <field7>data</field7>
    </fieldset>
    <fieldset>
        <field1>data</field1>
        <field2>data</field2>
        <field4>data</field4>
        <field9>data</field9>
    </fieldset>
</product>
<product>
    <productTitle>product nr 1</productTitle>
    <fieldset>
        <field1>data</field1>
        <field2>data</field2>
        <field4>data</field4>
        <field6>data</field6>
    </fieldset>
    <fieldset>
        <field1>data</field1>
        <field2>data</field2>
        <field3>data</field3>
        <field8>data</field8>
    </fieldset>
</product>
</xml>

Submitted by support on Thu, 2010-04-01 10:46

Hi Sybrand,

Given that you know the field names, a different method can be used to handle this scenario.

I assume that you are parsing using the Format String "xml|XML/PRODUCT/", in which case, a foreach() loop studying every key value would let you extract the fields into an array (which you can then pass to an external function to handle that fieldset), and also know when the fieldset was complete. You can pass any other variables required between myRecordHandler and myFieldSetHandler using global variables; For example:

  function myFieldSetHandler($fieldSet)
  {
    global $productTitle;
    // handle $fieldSet here, to study contents use
    print_r[$fieldSet];
  }
  function myRecordHandler($record)
  {
    global $productTitle;
    $productTitle = $record["PRODUCTTITLE"];
    $fieldSet = array();
    $inFieldSet = FALSE;
    foreach($record as $key => $value)
    {
      if ((strpos($key,"FIELDSET")!==FALSE) && ($value==""))
      {
        $inFieldSet = TRUE;
        if (count($fieldSet)) myFieldSetHandler($fieldSet);
        $fieldSet = array();
        continue;
      }
      if ($inFieldSet)
      {
        $fieldSet[$key] = $value;
      }
      if ((strpos($key,"FIELDSET")===FALSE) break;
    }
    if (count($fieldSet)) myFieldSetHandler($fieldSet);
  }

Hope this helps!
Let me know if you're not sure about any aspect of that structure.

All the best,
David.