0

This seams obvious, but what I found the most was how to manipulate existing XML and now I wish to build from ground zero. The source is a database converted into an Array. The root is a single "menu" and all child elements are called "item". The structure is defined by the value of "parent" property and "code" property.

item[0] ("code"=>"first" "somevar"=>"somevalue")
item[1] ("code"=>"second", "parent"=>"first" "somevar"=>"othervalue")

Means item[1] is a child of item[0].

<menu>
  <item code="first" somevar="somevalue">
    <item code="second" somevar="othervalue" />
  </item>
</menu>

There will be only two levels of items this time, maybe later I'll expand the capabilities to "n" levels...

I tried with SimpleXML, but it seams is too simple. So I tried with DOMDocument, but I'm stuck creating new elements...

$domMenu = new DOMDocument();
$domMenu->createElement("menu");
... creating the $domItem as a DOMElement with attributes ...
$domMenu->menu->appendChild($domItem);

This generates an error, it seams "menu" is not seen as an DOMElement. Should I use getElements methods or there is a better way of build this XML?

jszobody
  • 28,495
  • 6
  • 61
  • 72
Gustavo
  • 1,673
  • 4
  • 24
  • 39
  • 2
    maybe this can help you: http://stackoverflow.com/questions/1397036/how-to-convert-array-to-simplexml – Goikiu Jan 07 '14 at 14:01
  • Take a look at @Goikiu's link, but not the accepted answer: look at the 2 most popular answers below for a working solution. – Digital Chris Jan 07 '14 at 14:20
  • @DigitalChris I'm feeling nominate ._. – Goikiu Jan 07 '14 at 14:42
  • Interesting, I didn't know about this function. However, it build another kind of XML with nodes instead of attributes when building the elements. I don't wish to avoid the method because I wish to learn about XML manipulation with php. – Gustavo Jan 07 '14 at 15:39

2 Answers2

1

You did not append the menu element to the DOM. And DOM does not map element names to object properties like SimpleXML. The root element is accessible using the DOMDocument::$documentElement property.

$domMenu = new DOMDocument();
$domMenu->appendChild(
  $menuNode = $domMenu->createElement("menu")
);

... creating the $domItem as a DOMElement with attributes ...

$menuNode->appendChild($domItem);

In you case I would suggest using xpath to find the parent node for the itemNode and if not found let the function call itself (recursion) to append the parent element first. If here is not parent item, append the node to the document element.

$data = [
   ["code"=>"second", "parent"=>"first", "somevar"=>"othervalue"],
   ["code"=>"first", "somevar"=>"somevalue"]
];

function appendItem($xpath, $items, $item) {
  // create the new item node
  $itemNode = $xpath->document->createElement('item');
  $itemNode->setAttribute('code', $item['code']);
  $itemNode->setAttribute('somevar', $item['somevar']);

  $parentCode = isset($item['parent']) ? $item['parent'] : NULL;
  // does it have a parent and exists this parent in the $items array
  if (isset($parentCode) && isset($items[$parentCode])) {
    // fetch the existing parent
    $nodes = $xpath->evaluate('//item[@code = "'.$parentCode.'"]');
    if ($nodes->length > 0) {
      $parentNode = $nodes->item(0);
    } else  {
      // parent node not found create it
      $parentNode = appendItem($xpath, $items, $items[$parentCode]);
    }
  } else {
    $parentNode = $xpath->document->documentElement; 
  }
  $parentNode->appendChild($itemNode);
  return $itemNode;
}

$dom = new DOMDocument();
$xpath = new DOMXpath($dom);
$dom->appendChild(
  $dom->createElement("menu")
);

// build an indexed list using the "code" values
$items = [];
foreach ($data as $item) {
  $items[$item['code']] = $item;
}
foreach ($items as $item) {
  // check if the item has already been added
  if ($xpath->evaluate('count(//item[@code = "'.$item['code'].'"])') == 0) {
    // add it
    appendItem($xpath, $items, $item);
  }
}

$dom->formatOutput = TRUE;
echo $dom->saveXml();

Output:

<?xml version="1.0"?>
<menu>
  <item code="first" somevar="somevalue">
    <item code="second" somevar="othervalue"/>
  </item>
</menu>
ThW
  • 19,120
  • 3
  • 22
  • 44
  • You know, I was thinking about something like this, the point is keep the link of the element created with root. I'll fully test this... – Gustavo Jan 07 '14 at 15:44
  • Your method is right. The only thing is that the parent code maybe linked with a item that hasn't be created when the search is performed. I hasn't solved this for unlimited levels yet, with two levels it's easy, I create items without parent before items with parent value. But I marked as right answer because it is answering the question. Thanks! – Gustavo Jan 08 '14 at 13:39
  • Changed it to support unsorted items. – ThW Jan 08 '14 at 20:19
  • I did an adaptation for my menu and it works perfect! This code is very useful for creating categories of data in a database with hierarchy. In my case, I build the xml with php and my JavaScript code build the menu. Cool! – Gustavo Jan 09 '14 at 12:41
0
$xml = new SimpleXMLElement('<menu/>');
array_walk_recursive($array, array ($xml, 'addChild'));

print $xml->asXML();
Joran Den Houting
  • 3,149
  • 3
  • 21
  • 51
  • It looks like you nabbed this solution from here: http://stackoverflow.com/questions/1397036/how-to-convert-array-to-simplexml but the accepted answer given doesn't work for this. It flattens everything down to one level. – Digital Chris Jan 07 '14 at 14:19
  • Please provide any description of your answer. – Wahyu Kristianto May 22 '14 at 03:18