Recently 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.
Pingback: PHP DOMDocument: Convert Array to Xml | devexp | Webmaster Tools
Pingback: PHP DOMDocument: Convert Array to Xml | devexp
Pingback: PHP DOMDocument: Convert Array to Xml | devexp | The Hoover
This is a great method. Have you ever did the reverse? DOMDocument to array?
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
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;
}
}
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;
}
Pingback: PHP DOMDocument: Convert Array to Xml | devexp node
To wrap stirngs in CDATA:
…
} else {
if(is_numeric($mixed)){
$textNode = $this->createTextNode($mixed);
} else {
$textNode = $this->createCDATASection($mixed);
}
$domElement->appendChild($textNode);
}
…
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
.
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.
This code it’s just beautiful!!!!!!!!
Thanks a lot for this post, it is exactly what I needed to work arrays quickly into xml.
Thank you good sir! I was searching for this high and low last night. I was building an lib to interact with a payment gateway XML API. I used your class here:
https://github.com/jboesch/PsigateAccountManager
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