0

How do I iterate through an XML file like shown below? It's output without any root or parent nodes. Some elements repeat, but some don't. And, it's a large file output from a legacy application.

<name>Chair</name>
<price>$53</price>
<quantity>20</quantity>
<units>Piece</units>
<name>Lamp</name>
<price>$20</price>
<quantity>90</quantity>
<units>Piece</units>
<name>Table</name>
<price>$35</price>
<quantity>10</quantity>
<units>Piece</units>
<material>Wood</material>
<name>Pen Holder</name>
<price>$5</price>
<quantity>20</quantity>
<units>Piece</units>
<color>Black</color>

This is how I do it otherwise, but it wont work with this.

$data=simplexml_load_file("inventory.xml");

foreach($data->item as $item) {
        echo "Name: " . $item->name . "<br>";
        echo "Price: " . $item->price . "<br>";
        echo "Quantity: " . $item->quantity . "<br>";
        echo "Units: " . $item->units . "<br>";
        echo "Color: " . $item->color . "<br>";
}
4
  • Is there any way you could pre-process it? Like put a root element; put a parent tag; iterate line-by line taking note of the tags you encounter; as soon as you find one you already saw, that means the start of another object, then put closing and opening tags there; once you're out of input close the last parent and close the root. Now you have valid xml. Commented Jun 18, 2018 at 7:10
  • I take fixing the legacy app instead is not an option? Commented Jun 18, 2018 at 7:10
  • Either way you will be stuck with writing your own parser - you'll need it to pre-process the file so you'd be able to use standard XML parser afterwards or you'll just parse and use the data. Commented Jun 18, 2018 at 7:17
  • you could either fix the system that spews out the invalid xml and turn it into a valid one, or just treat the whole thing as a string and parse it yourself. Commented Jun 18, 2018 at 7:33

1 Answer 1

2

Adding a root element is easy. You just load the XML into a string and then append and prepend as needed. However, grouping the various elements in items is a bit trickier and largely depends on the XML. The following code will work with the XML you show:

<?php

$xml = 'your xml from the question';

$dom = new DOMDocument;
$dom->loadXml("<root>$xml</root>");

$fixed = new DOMDocument();
$fixed->loadXML("<inventory><items/></inventory>");
$fixed->formatOutput = true;

$items = $fixed->getElementsByTagName('items')->item(0);
foreach ($dom->documentElement->childNodes as $node) {
    if ($node->nodeName === 'name') {
        $item = $fixed->createElement('item');
        $item->appendChild($fixed->createElement($node->nodeName, $node->nodeValue));
        $next = $node->nextSibling;
        while ($next !== null) {
            if ($next instanceof DOMElement) {
                if ($next->nodeName !== 'name') {
                    $item->appendChild($fixed->createElement($next->nodeName, $next->nodeValue));
                } else {
                    $items->appendChild($item);
                    break;
                }
            }
            $next = $next->nextSibling;
        }
    }
}
echo $fixed->saveXML();

This will create two documents:

  1. Your legacy XML with a dummy <root> element so we can process it
  2. A document with the root element <inventory> and an empty element <items>.

We will then iterate all the elements in the legacy XML. When we find a <name> element, we create a new <item> element and add the <name> element as a child. We then check every following sibling to the <name> element. If it's not a <name> element, we will add it to the <item> as well. When it's another <name>, we add the <item> to the <items> collection and start over.

This will then produce:

<?xml version="1.0"?>
<inventory>
  <items>
    <item>
      <name>Chair</name>
      <price>$53</price>
      <quantity>20</quantity>
      <units>Piece</units>
    </item>
    <item>
      <name>Lamp</name>
      <price>$20</price>
      <quantity>90</quantity>
      <units>Piece</units>
    </item>
    <item>
      <name>Table</name>
      <price>$35</price>
      <quantity>10</quantity>
      <units>Piece</units>
      <material>Wood</material>
    </item>
  </items>
</inventory>

You can probably do all of this in a single document. I felt it was easier to understand with two documents.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.