Search code examples
phpxmlxmlreader

PHP XMLREADER Nested Elements


Hi I am trying to use XML Reader to read some dynamic xml where I don't know the element names. I need to use XML Reader and not SimpleXML as that is all that is available on my server. The trouble I am having is trying to have something that can deal with both nested and un-nested elements. My XML looks like

<status>
  <state>active</state>
  <time>2014-07-15T04:00:00.000Z</time>
    <output-type>archive</output-type>
   <sources>
<source>
<class>audio</class>
<channels>
    <channel>
        <position>left</position>
        <average>0</average>
        <peak>0</peak>
    </channel>
    <channel>
        <position>right</position>
        <average>1095</average>
        <peak>1114</peak>
    </channel>
</channels>
</source>
</status>

But Could also look like:

<status>inactive</status>

I am using the following code:

    while ($reader->read()) {
        if ($reader->nodeType == XMLReader::ELEMENT) {
            $element = $reader->name;
            //$elementarray[] = $element;
            echo $element;
            if ($reader->depth <1){echo"</br>";}
        }

        if ($reader->nodeType == XMLReader::TEXT) {
            $value = $reader->value;
            $valuearray[] = $value;
            echo " : ".$value."</br>";                 
        }
    }

This outputs

1stElement2ndelement : text

I cannot find anyway in the poorly documented XML Reader to have the output

1stElement:
2ndelement: text

which I can populate an array with.

Any help is appreciated as I am new to PHP.


Solution

  • Do not post pseudo xml. This is not valid xml (element names can not start with a digit). It makes it difficult to understand/validate your problem. Is DOM available? It would be strange to have only XMLReader and not DOM. With DOM you have Xpath and that is a lot easier to use.

    So let's assume some options xml.

    $xml = <<<'XML'
    <options>
      <one>1</one>
      <two>2</two>
    </options>
    XML;
    

    XML reader will read each node. Remember the name of the last element node. If a text node is found and not empty add the content with the remembered name to a result array.

    $reader = new XMLReader();
    $reader->open('data:/text/plain,'.urlencode($xml));
    
    $result = [];
    $element = null;
    while ($reader->read()) {
      switch ($reader->nodeType) {
      case XMLReader::ELEMENT :
        $element = $reader->name;
        break;
      case XMLReader::TEXT :
        $value = $reader->value;
        if (trim($value) != '') {
          $result[$element] = $value;
        }
        break;
      }
    }
    
    var_dump($result);
    

    Output:

    array(2) {
      ["one"]=>
      string(1) "1"
      ["two"]=>
      string(1) "2"
    }
    

    In DOM it is possible to just select the element nodes into a list and iterate them:

    $dom = new DOMDocument();
    $dom->loadXml($xml);
    $xpath = new DOMXpath($dom);
    
    $result = [];
    foreach ($xpath->evaluate('/options/*') as $node) {
      $result[$node->localName] = $node->nodeValue;
    }
    
    var_dump($result);
    

    Demo: https://eval.in/170803

    DOMXpath::evaluate() allows you make your expressions relative to a context node and to fetch scalars. So you mostly will either fetch a scalar or a node list you can iterate. The result depends on the Xpath expression, so you will always know.

    $dom = new DOMDocument();
    $dom->loadXml($xml);
    $xpath = new DOMXpath($dom);
    
    foreach ($xpath->evaluate('//channels/channel') as $channel) {
      var_dump(
        $xpath->evaluate('string(position)', $channel),
        $xpath->evaluate('number(1095)', $channel),
        $xpath->evaluate('number(1114)', $channel)
      );
    }
    

    Demo: https://eval.in/172551