PHP DOMDocument: Convert Array to Xml

phpRecently I start working on an export module to invoice applications. One of them uses a very simple xml structure (simple nodes without attributes etc.) and therefore I wanted to create this xml also in a very simple way: from an array.

I decided the use DOM, and not e.g. SimpleXML, because I need to validate my xml with a schema.

After a quick search on the internet I found several snippets that could do the trick until I had to set the same element tag name on the same level:

<nodes>
	<node>text</node>
	<node>text</node>
</nodes>

The snippets I found use the index of an array to create the name of the element and the value for the text part. Only everyone knows that you cannot set 2 keys with the same name in an array.

So what could we do to improve this ?


I changed a snippet so it can generate the example above. The array should now look like this:

array (
	“nodes” => array (
		“node” => array (
			0 => “text”
			1 => “text”
		)
	)
)

Since you cannot set integers as element tags, it checks if the index is an integer and if it is it loops the array to recreate the same element tag name with the new value.

I added the function in a class which extends the DOMDocument class (XmlDomConstruct):

<?php
/**
 * Extends the DOMDocument to implement personal (utility) methods.
 *
 * @author Toni Van de Voorde
 */
class XmlDomConstruct extends DOMDocument {

	/**
	 * Constructs elements and texts from an array or string.
	 * The array can contain an element's name in the index part
	 * and an element's text in the value part.
	 * 
	 * It can also creates an xml with the same element tagName on the same 
	 * level.
	 * 
	 * ex:
	 * <nodes>
	 *   <node>text</node>
	 *   <node>
	 *     <field>hello</field>
	 *     <field>world</field>
	 *   </node>
	 * </nodes>
	 * 
	 * Array should then look like:
	 * 
	 * Array (
	 *   "nodes" => Array (
	 *     "node" => Array (
	 *       0 => "text"
	 *       1 => Array (
	 *         "field" => Array (
	 *           0 => "hello"
	 *           1 => "world"
	 *         )
	 *       )
	 *     )
	 *   )
	 * )
	 *
	 * @param mixed $mixed An array or string.
	 * 
	 * @param DOMElement[optional] $domElement Then element
	 * from where the array will be construct to.
	 * 
	 */
	public function fromMixed($mixed, DOMElement $domElement = null) {

		$domElement = is_null($domElement) ? $this : $domElement;

		if (is_array($mixed)) {
			foreach( $mixed as $index => $mixedElement ) {

				if ( is_int($index) ) {
					if ( $index == 0 ) {
						$node = $domElement;
					} else {
						$node = $this->createElement($domElement->tagName);
						$domElement->parentNode->appendChild($node);
					}
				} 
				
				else {
					$node = $this->createElement($index);
					$domElement->appendChild($node);
				}
				
				$this->fromMixed($mixedElement, $node);
				
			}
		} else {
			$domElement->appendChild($this->createTextNode($mixed));
		}
		
	}
	
}
?>

And here is a very simple example on how you can use this class:

$array = array(
  "nodes" => array(
    "node" => array(
      0 => "text",
      1 => "text"
)));

$this->dom = new XmlDomConstruct('1.0', 'utf-8');
$this->dom->fromMixed($array);

echo $this->dom->saveXML();

Isn’t that wonderful? You don’t even have to know how to construct xmls with the DOMDocument object. Of course this will only work for simple xmls.

Maybe, if I have the time, I’ll try to add the possibility to insert attributes to elements.

20 thoughts on “PHP DOMDocument: Convert Array to Xml

  1. Pingback: PHP DOMDocument: Convert Array to Xml | devexp | Webmaster Tools

  2. Pingback: PHP DOMDocument: Convert Array to Xml | devexp

  3. Pingback: PHP DOMDocument: Convert Array to Xml | devexp | The Hoover

  4. This is a great method. Have you ever did the reverse? DOMDocument to array?

  5. Hi Chad,

    No I didn’t. But it could be a good addition to this method. Maybe when I find the time I’ll do it for fun.

    Thx for your comment ;)

  6. I worked on this last night. Thought you might find it useful

    /**
    * Converts a given DOMNode to an array or string, if no DOMNode is supplied
    * the method will use $this
    *
    * @param DOMNode $oDomNode the node to transform
    *
    * @return array|string
    */
    public function toArray(DOMNode $oDomNode = null)
    {
    // return empty array if dom is blank
    if (is_null($oDomNode) && !$this->hasChildNodes()) {
    return array();
    }

    $oDomNode = (is_null($oDomNode)) ? $this->documentElement : $oDomNode;
    if (!$oDomNode->hasChildNodes()) {
    return $oDomNode->nodeValue;
    } else {
    $arResult = array();
    foreach ($oDomNode->childNodes as $oChildNode) {
    $sKey = ($oChildNode->parentNode instanceof DOMElement ) ? $oChildNode->parentNode->nodeName : $oChildNode->nodeName;
    $mValue = $this->toArray($oChildNode);
    if (!isset($arResult[$sKey])) {
    $arResult[$sKey] = $mValue;
    // result[foo] = bar
    } else {
    if (!is_array($arResult[$sKey])) {
    // this is the second time adding a value for $sKey, so convert $sKeys element to an array
    // result[foo] = bar
    $sTemp = $arResult[$sKey]; // bar
    $arResult[$sKey] = array(); // result[foo] = array()
    $arResult[$sKey][] = $sTemp; // result[foo] = array(bar)
    }
    if (!is_array($mValue)) {
    $arResult[$sKey][] = $mValue;
    } else {
    $arValueEach = each($mValue);
    $arResultEach = each($arResult[$sKey]);
    reset($arResult[$sKey]);
    if ($arValueEach[‘key’] == $arResultEach[‘key’]) {
    // if result[key] = array(foo=>mixed)
    // and value = array(foo=>mixed)
    // then we want result[key] = array(foo=>array(mixed, array))
    if (!is_array($arResult[$sKey][$arResultEach[‘key’]])
    || !isset($arResult[$sKey][$arResultEach[‘key’]][0])) {
    $arResult[$sKey] = array();
    $arResult[$sKey][$arResultEach[‘key’]] = array();
    $arResult[$sKey][$arResultEach[‘key’]][] = $arResultEach[‘value’];
    }
    $arResult[$sKey][$arResultEach[‘key’]][] = $arValueEach[‘value’];
    } else {
    // else just merge the array
    $arResult[$sKey] = array_merge($arResult[$sKey], $mValue);
    }
    }
    }
    }
    return $arResult;
    }
    }

  7. Here is a much better toArray() method. Enjoy

    public function toArray(DOMNode $oDomNode = null)
    {
    // return empty array if dom is blank
    if (is_null($oDomNode) && !$this->hasChildNodes()) {
    return array();
    }
    $oDomNode = (is_null($oDomNode)) ? $this->documentElement : $oDomNode;
    $arResult = array();
    if (!$oDomNode->hasChildNodes()) {
    $arResult[$oDomNode->nodeName] = $oDomNode->nodeValue;
    } else {
    foreach ($oDomNode->childNodes as $oChildNode) {
    // how many of these child nodes do we have?
    $oChildNodeList = $oDomNode->getElementsByTagName($oChildNode->nodeName); // count = 0
    $iChildCount = 0;
    // there are x number of childs in this node that have the same tag name
    // however, we are only interested in the # of siblings with the same tag name
    foreach ($oChildNodeList as $oNode) {
    if ($oNode->parentNode->isSameNode($oChildNode->parentNode)) {
    $iChildCount++;
    }
    }
    $mValue = $this->toArray($oChildNode);
    $mValue = is_array($mValue) ? $mValue[$oChildNode->nodeName] : $mValue;
    $sKey = ($oChildNode->nodeName{0} == ‘#’) ? 0 : $oChildNode->nodeName;
    // this will give us a clue as to what the result structure should be
    // how many of thse child nodes do we have?
    if ($iChildCount == 1) { // only one child – make associative array
    $arResult[$sKey] = $mValue;
    } elseif ($iChildCount > 1) { // more than one child like this – make numeric array
    $arResult[$sKey][] = $mValue;
    } elseif ($iChildCount == 0) { // no child records found, this is DOMText or DOMCDataSection
    $arResult[$sKey] = $mValue;
    }
    }
    // if the child is bar, the result will be array(bar)
    // make the result just ‘bar’
    if (count($arResult) == 1 && isset($arResult[0]) && !is_array($arResult[0])) {
    $arResult = $arResult[0];
    }
    $arResult = array($oDomNode->nodeName=>$arResult);
    }
    // get our attributes if we have any
    if ($oDomNode->hasAttributes()) {
    foreach ($oDomNode->attributes as $sAttrName=>$oAttrNode) {
    // retain namespace prefixes
    $arResult[“@{$oAttrNode->nodeName}”] = $oAttrNode->nodeValue;
    }
    }
    return $arResult;
    }

  8. Pingback: PHP DOMDocument: Convert Array to Xml | devexp node

  9. To wrap stirngs in CDATA:

    } else {

    if(is_numeric($mixed)){
    $textNode = $this->createTextNode($mixed);
    } else {
    $textNode = $this->createCDATASection($mixed);
    }

    $domElement->appendChild($textNode);
    }

  10. Here’s a class I wrote that converts an array to a DOMDocument object: code.google.com/p/array-to-domdocument/.

    It lets you add attributes too. Sorry about marketing it here :D.

    • Hi Omer,

      Thx for the link. I will try this out when I have the time. At first look it seem great + with support for attributes.

      Good work

      grtz

      • Thanks, Van de Voorde! Please let me know if there is anything in the code that needs improvement.

  11. Worked a lot with your function but didn’t work with special chars on data (like ê) and didn’t work too when the value of an array is = ” (not null just blank ”) so I changed to:

    public function fromMixed($mixed, DOMElement $domElement = null) {

    $domElement = is_null($domElement) ? $this : $domElement;

    if (is_array($mixed)) {
    foreach ($mixed as $index => $mixedElement) {

    if (is_int($index)) {
    if ($index == 0) {
    $node = $domElement;
    } else {
    $node = $this->createElement($domElement->tagName);
    $domElement->parentNode->appendChild($node);
    }
    } else {
    if ($index != ”) {
    $node = $this->createElement($index);
    $domElement->appendChild($node);
    }
    }
    if (!is_array($mixedElement)) {
    $mixedElement = utf8_encode($mixedElement);
    }
    $this->fromMixed($mixedElement, $node);
    }
    } else {
    $domElement->appendChild($this->createTextNode($mixed));
    }
    }

    Maybe isn’t the best but I share with you what I’ve worked on… Thanks again for your function :)

  12. Toni,

    Thanks very much for posting this! It helped me a ton — with exactly the same problem!

    Note: I did post your link to a couple of questions over on Stack Overflow.

    Here’s what I found to take XML and convert it to an array:

    From http://www.php.net/manual/en/simplexml.examples-basic.php

    json_decode( json_encode( simplexml_load_string( $string ) ), TRUE );

  13. For the sake of information, i am presenting a good example on how to use this class:

    dom = new XmlDomConstruct(‘1.0′, ‘utf-8′);
    $this->dom->fromMixed($array);
    echo $this->dom->saveXML();
    }
    }

    $d = new MyDom;
    $array = array(
    “nodes” => array(
    “node” => array(
    0 => “text1″,
    1 => “text2″
    )));

    $a = $d->generateDom($array);
    header(“Content-type: text/xml”);
    echo $a;
    ?>

  14. For the sake of information, i am presenting a good example on how to use this class:


    dom = new XmlDomConstruct('1.0', 'utf-8');
    $this->dom->fromMixed($array);
    echo $this->dom->saveXML();
    }
    }

    $d = new MyDom;
    $array = array(
    "nodes" => array(
    "node" => array(
    0 => "text1",
    1 => "text2"
    )));

    $a = $d->generateDom($array);
    header("Content-type: text/xml");
    echo $a;
    ?>

  15. Sorry, my above comments have some code missing:

    dom = new XmlDomConstruct(‘1.0′, ‘utf-8′);
    $this->dom->fromMixed($array);
    echo $this->dom->saveXML();
    }
    }

    $d = new MyDom;
    $array = array(
    “nodes” => array(
    “node” => array(
    0 => “text1″,
    1 => “text2″
    )));

    $a = $d->generateDom($array);
    header(“Content-type: text/xml”);
    echo $a;
    ?>

Comments are closed.