<?php

class my_xmlreader implements Iterator
{
    /**
     * Field Separator;
     */
    protected $_separator = '_';

/**
 * XML file path
 *
 * @var string
 */
protected $_file = null;

/**
 * XML reader object
 *
 * @var XMLReader
 */
protected $_reader = null;

/**
 * Current _node
 *
 * @var array
 */
protected $_node = null;

/**
 * Dummy-key for iteration.
 * Has no real value except maybe act as a counter
 *
 * @var integer
 */
protected $_key = 0;

/**
 * Level of depth we are at.
 *
 * @var int $depth
 */
protected $_depth = 0;

/**
 * Warpping element name.
 *
 * @var string
 */
protected $_mainElement = null;

/**
 * Pass this method the $file to be parsed.
 *
 * @param   string  $file
 */
function __construct($file)
{
    $this->_file = $file;
}

/**
 * @see Iterator::current()
 */
public function current()
{
    return $this->_node;
}

/**
 * @see Iterator::key()
 */
public function key()
{
    return $this->_key;
}

/**
 * @see Iterator::next()
 */
public function next()
{
    $this->_node = null;
}

/**
 * @see Iterator::rewind()
 */
public function rewind()
{
    $this->_reader  = new XMLReader();
    $this->_reader->open($this->_file);
    $this->_node    = null;
    $this->_key     = 0;
}

/**
 * @see Iterator::valid()
 */
public function valid()
{
    if (is_null($this->_node)) {
        $this->_loadNext();
    }

    return (!is_null($this->_node));
}

/**
 * Loads the next _node
 *
 * @return void
 */
protected function _loadNext()
{
    $capture    = false;
    $values     = array();
    $nest       = array();
    $s          = $this->_separator;

    while ($this->_reader->read()) {

        switch ($this->_reader->nodeType) {

            case XMLReader::ELEMENT:

                if (1 === $this->_depth) {
                    $this->_mainElement = $this->_reader->name;
                    //$nest[] = $this->_reader->name;

                    if ($this->_reader->hasAttributes) {
                        $count = $this->_reader->attributeCount;

                        for($n=0; $n<$count; $n++) {
                            $this->_reader->moveToAttributeNo($n);
                            $nest[] = 'attr' . $s . $this->_reader->localName;
                            $values[implode($s, $nest)] = $this->_reader->value;
                            /*$nest[] = $this->_reader->name . '_attribue_' . ($n+1);
                            $values[implode('_', $nest)] = $this->_reader->getAttributeNo($n);*/
                            array_pop($nest);
                        }

                        // Hack to get back to the element we were on.
                        $this->_reader->moveToElement($this->_reader->name);

                        unset($n, $count);
                    }
                }

                if ($capture) {
                    $nest[] = $this->_reader->name;
                    $values[implode($s, $nest)] = null;

                    if ($this->_reader->hasAttributes) {
                        $count = $this->_reader->attributeCount;

                        for($n=0; $n<$count; $n++) {
                            $this->_reader->moveToAttributeNo($n);

                            $nest[] = 'attr' . $s . $this->_reader->localName;
                            $values[implode($s, $nest)] = $this->_reader->value;
                            array_pop($nest);
                        }

                        // Hack to get back to the element we were on.
                        $this->_reader->moveToElement($this->_reader->name);

                        unset($n, $count);
                    }
                }

                if ($this->_reader->name == $this->_mainElement) {

                    $capture = true;
                }

                //echo 'Main element: ' . $this->_reader->name . PHP_EOL;
                //echo $this->_depth . PHP_EOL;

                ++$this->_depth;

                break;

            case XMLReader::TEXT:

                if ($capture) {
                    $values[implode($s, $nest)] = $this->_reader->value;
                }

                break;

            case XMLReader::END_ELEMENT:

                $this->_depth --;

                if ($this->_reader->name == $this->_mainElement) {

                    $this->_node = $values;
                    $this->_key++;
                    //$this->_depth = 1;
                    break 2;
                }

                if ($capture) {
                    array_pop($nest);
                }

                break;
        }
    }
}
}
?>

<?php

require_once 'classes/my_xmlreader.php';

class XmlAdapter
{
    protected $_xml = null;
    protected $_file = null;

/**
 * @param   $file
 */
public function __construct($file = null)
{
    $this->_file = $file;

    $this->_xml = new my_xmlreader($this->_file);
    $this->_xml->rewind();
}

/**
 * @return  array
 */
public function getFieldNames()
{
    $this->reset();

    $output = array();

    // I have tried using the "my_xmlreader" itterator with a foreach loop,
    // hoever, I get the same problem.

    /*$i = 0;
    foreach ($this->_xml as $key) {
        $output = array_merge($output, $key);
        $i ++;
        if ($i > 3) {
            echo 'More than 20';
            break 1;
        }
    }*/

    // After 3 rows, return with field names.
    $i = 0;
    while ($record = $this->getRecord()) {

        $output = array_merge($output, $record);
        $i++;
        if ($i > 3) {
            break;
        }
    }

    $headers = array();

    foreach ($output as $k => $v) {
        $headers[] = $k;
    }

    // Reset to the beginning of the file after getting field names.
    $this->reset();

    return $headers;
}

public function reset()
{
    $this->_xml->rewind();
}

public function getRecord()
{
    $current = null;

    if ($this->_xml->valid()) {

        $current = $this->_xml->current();
        //echo print_r($current, 1) . PHP_EOL;
        $this->_xml->next();
    }

    return $current;
}
}

// File contents shown above.
$file = 'testfeed.xml';

$xml = new XmlAdapter($file);

// Now when we do this, we get no rows from the getRecord() method,
// comment this out and getRecord() returns correct result as expected.
$headers = $xml->getFieldNames();
print_r($headers);

// Comment the two lines above out and this works... When not commented, it only returns 1 row?
while (!is_null($record = $xml->getRecord())) {
    print_r($record);
}

As you can see, when the getFieldNames() method is run and there are more than 3 rows, we break (exit the loop) and return the rows we have accumulated. We merge the fields and returnt them. The assumption is that all the field headers will be in the first 3 records.

So what's the problem?

After the "break" has been executed in the getFieldNames(), the getRecord() method no longer returns anything. However, if I call the getRecord() without calling getFieldNames() it correctly returns the 5 records from the sample xml file.

    Contents of the XML file:

    <?xml version="1.0" encoding="utf-8"?>
    <rootNode>
        <product sku="6767" language="EN">
            <name mpn="Test Product MPN 1">Test Product Name 1</name>
            <description>Test product description 1</description>
            <attributes>
                <colour>red</colour>
                <size>medium</size>
            </attributes>
            <features>
                <main>
                    <surround>
                        <type>5.1</type>
                        <speakers>2</speakers>
                        <power>2 x 100w</power>
                    </surround>
                </main>
                <technical>
                    <power>
                        <voltage>240v</voltage>
                        <current>1000w</current>
                    </power>
                </technical>
            </features>
            <price currency="GBP">19</price>
            <category>Test Category Name 1</category>
        </product>
        <product sku="7097" language="FR">
            <name mpn="Test Product MPN 2">Test Product Name 2</name>
            <description>Test product description 2</description>
            <attributes>
                <colour>blue</colour>
                <size>large</size>
            </attributes>
            <price currency="USD">82</price>
            <category>Test Category Name 2</category>
        </product>
        <product sku="5825" language="EN">
            <name mpn="Test Product MPN 3">Test Product Name 3</name>
            <description>Test product description 3</description>
            <attributes>
                <colour>red</colour>
                <size>small</size>
            </attributes>
            <features>
                <main>
                    <surround>
                        <type>5.1</type>
                        <speakers>2</speakers>
                        <power>2 x 100w</power>
                    </surround>
                </main>
                <technical>
                    <power>
                        <voltage>240v</voltage>
                        <current>1000w</current>
                    </power>
                </technical>
            </features>
            <price currency="GBP">90</price>
            <category>Test Category Name 3</category>
        </product>
        <product sku="5470" language="FR">
            <name mpn="Test Product MPN 4">Test Product Name 4</name>
            <description>Test product description 4</description>
            <attributes>
                <colour>blue</colour>
                <size>medium</size>
            </attributes>
            <price currency="USD">56</price>
            <category>Test Category Name 4</category>
        </product>
        <product sku="1094" language="EN">
            <name mpn="Test Product MPN 5">Test Product Name 5</name>
            <description>Test product description 5</description>
            <attributes>
                <colour>red</colour>
                <size>small</size>
            </attributes>
            <features>
                <main>
                    <surround>
                        <type>5.1</type>
                        <speakers>2</speakers>
                        <power>2 x 100w</power>
                    </surround>
                </main>
                <technical>
                    <power>
                        <voltage>240v</voltage>
                        <current>1000w</current>
                    </power>
                </technical>
            </features>
            <price currency="GBP">170</price>
            <category>Test Category Name 5</category>
        </product>
    </rootNode>
    

      Result

      
      Array
      (
          [0] => attr_sku
          [1] => attr_language
          [2] => name
          [3] => name_attr_mpn
          [4] => description
          [5] => attributes
          [6] => attributes_colour
          [7] => attributes_size
          [8] => features
          [9] => features_main
          [10] => features_main_surround
          [11] => features_main_surround_type
          [12] => features_main_surround_speakers
          [13] => features_main_surround_power
          [14] => features_technical
          [15] => features_technical_power
          [16] => features_technical_power_voltage
          [17] => features_technical_power_current
          [18] => price
          [19] => price_attr_currency
          [20] => category
      )
      Array
      (
          [product] => 
          [product_attr_sku] => 1094
          [product_attr_language] => EN
          [product_name] => Test Product Name 5
          [product_name_attr_mpn] => Test Product MPN 5
          [product_description] => Test product description 5
          [product_attributes] => 
          [product_attributes_colour] => red
          [product_attributes_size] => small
          [product_features] => 
          [product_features_main] => 
          [product_features_main_surround] => 
          [product_features_main_surround_type] => 5.1
          [product_features_main_surround_speakers] => 2
          [product_features_main_surround_power] => 2 x 100w
          [product_features_technical] => 
          [product_features_technical_power] => 
          [product_features_technical_power_voltage] => 240v
          [product_features_technical_power_current] => 1000w
          [product_price] => 170
          [product_price_attr_currency] => GBP
          [product_category] => Test Category Name 5
      )
      
      
            
        $i = 0; while ($record = $this->getRecord()) { $i++; if ($i > 3) { continue; } $output = array_merge($output, $record); }

        Update:

        If I change this to continue it works as expected, however, with a huge xml file (2000+ records), it iterates through all 2000+ records to get the headers.

        The idea is to stop at 3, but this seems to where the problem lies.

        Also, I have noticed that in my example XML I have 5 records, it seems to only output the last record (record 5) from the XML file after the getFieldNames() method has been run.

        Even when I change the sample xml to have 2000 records, it only outputs record number 2000. (The last record)

        Again, though, when I do not call getFieldNames() everything works as expected.

          SOLVED:

          The rewind() method of the my_xmlreader (Itterator) class did not reset the $this->_depth to zero. With that change implemented, the class works as expected.

              /**
               * @see Iterator::rewind()
               */
              public function rewind()
              {
                  $this->_reader  = new XMLReader();
                  $this->_reader->open($this->_file);
                  $this->_node    = null;
                  $this->_key     = 0;
                  $this->_depth   = 0;
              }
          
            Write a Reply...