Php: Serializing xml to object

Posted on August 24, 2009

9


Not finding a xml-to-object solution that “normalizes” the result as far as possible down to object level , I decided to create one myself.

  • Single item nodes and attributes are added as objects
  • Multi item nodes are added as array of objects

I’m sure that this has already been done a thousand times before, in easier and more elegant ways, but what the heck! – spending life programming is fun! 🙂

The following wierd test-xml (intentionally badly structured!)…

<root>
	<car brand="Volvo" >
		<equipments>
			<equipment type="motor" style="strong" />
			<equipment type="wheels" style="fast" />
		</equipments>
	</car>
	<car brand="Saab" />
	<car brand="Porsche" />
	<dog name="Pluto" />
	<books>
		<book title="Da Vinci code">
			<author name="Dan Brown" pages="400"/>
		</book>
		<book title="The Holy Bible" pages="999">
			<author name="Matthew" />
			<author name="Mark" />
			<author name="Luke" />
			<author name="John" />
		</book>
	</books>
	<car brand="Mercedes" />
	<person name="John" age="23" />
	<orphan />
	<dog name="Lassie" />
	<person name="Pete" age="32" weight="75" />
</root>

…gives the following result object:

stdClass Object (
  [car] => Array (
      [0] => stdClass Object (
          [brand] => Volvo
          [equipments] => stdClass Object (
              [equipment] => Array  (
                  [0] => stdClass Object (
                      [type] => motor
                      [style] => strong
                    )

                  [1] => stdClass Object (
                      [type] => wheels
                      [style] => fast
                    )
                )
            )
        )
      [1] => stdClass Object (
          [brand] => Saab
        )
      [2] => stdClass Object (
          [brand] => Porsche
        )
      [3] => stdClass Object (
          [brand] => Mercedes
        )
    )
  [dog] => Array (
      [0] => stdClass Object (
          [name] => Pluto
        )
      [1] => stdClass Object (
          [name] => Lassie
        )
    )
  [books] => stdClass Object (
      [book] => Array         (
          [0] => stdClass Object (
              [title] => Da Vinci code
              [author] => stdClass Object (
                  [name] => Dan Brown
                  [pages] => 400
                )
            )
          [1] => stdClass Object (
              [title] => The Holy Bible
              [pages] => 999
              [author] => Array (
                  [0] => stdClass Object (
                      [name] => Matthew
                    )
                  [1] => stdClass Object (
                      [name] => Mark
                    )
                  [2] => stdClass Object (
                      [name] => Luke
                    )
                  [3] => stdClass Object (
                      [name] => John
                    )
                )
            )
        )
    )
  [person] => Array (
      [0] => stdClass Object (
          [name] => John
          [age] => 23
        )
      [1] => stdClass Object (
          [name] => Pete
          [age] => 32
          [weight] => 75
        )
    )
  [orphan] => stdClass Object (
    )
)

And here’s the source code, implemented as a static function in XmlUtils class:

Class XmlUtils {

	public static function xmlToObject($xml, $obj = null) {
		if (!$obj) $obj = new StdClass();
		//**********************************************************
		// Create array of unique node names
		$uniqueNodeNames = array();
		foreach ($xml->children() as $xmlChild) {
			@$uniqueNodeNames[$xmlChild->getName()]++;
		}
		//**********************************************************
		// Create child types - object for single nodes, array of objects for multi nodes:
		foreach ($uniqueNodeNames as $nodeName => $nodeCount) {
			if ($nodeCount > 1) {
				$obj->$nodeName = array();
				for ($i=0; $i<$nodeCount; $i++) {
					array_push($obj->$nodeName, new StdClass());
				}
			} else {
				$obj->$nodeName = new StdClass();
			}
		}
		//**********************************************************
		// For each child node: add attributes as object properties and invoke recursion
		$arrayIdx = array();
		foreach ($xml->children() as $xmlChild) {
			$str = trim($xmlChild);
			//print_r($xmlChild->attributes());
			$nodeText = trim($xmlChild);
			$nodeName = $xmlChild->getName();
			// If child is array
			if (is_array($obj->$nodeName)) {
				$idx = (int)@$arrayIdx[$nodeName];
				$objArray = $obj->$nodeName;
				// Add attributes as object properties
				foreach($xmlChild->attributes() as $attributeType => $attributeValue) {
					$objArray[$idx]->$attributeType = (string)$attributeValue;
				}
				// If element text (e.g. <node>ElementText<node>
				if (strlen($nodeText)) $objArray[$idx]->$nodeName = $nodeText;
				// Invoke recursion
				XmlUtils::xmlToObject($xmlChild, $objArray[$idx]);
			}
			// If child is object
			if (is_object($obj->$nodeName)) {
				// Add attributes as object properties
				foreach($xmlChild->attributes() as $attributeType => $attributeValue) {
					$obj->$nodeName->$attributeType = (string)$attributeValue;
				}
				// If element text (e.g. <node>ElementText<node>
				if (strlen($nodeText)) $obj->$nodeName->$nodeName = $nodeText;
				// Invoke recursion
				XmlUtils::xmlToObject($xmlChild, $obj->$nodeName);
			}
			@$arrayIdx[$nodeName]++;
		}
		return $obj;
	}

	public static function xmlFileToObject($xmlFileName) {
		if (!file_exists($xmlFileName)) die ("XmlUtils::xmlFileToObject Error: $xmlFileName nonexistent!");
		$xml = simplexml_load_file($xmlFileName);
		return XmlUtils::xmlToObject($xml);
	}
}
//**********************************
// Example usage:
$xmlFile = 'test.xml';
$resultObj = XmlUtils::xmlFileToObject($xmlFile);

Text element nodes (I hardly ever use them!) are added as an object property with the same name as the node object itself:

<root>
	<book author="Old McDonald">Once upon a time...</book>
</root>'

thus gives the following object:

stdClass Object (
  [book] => stdClass Object (
    [author] => Old McDonald
    [book] => Once upon a time...
  )
)

Of course they could be added as strings (or array of strings), but as soon as the node has an attribute, it has to be treated as an object anyway…

 

Cheers!

Advertisements
Tagged: , ,
Posted in: PHP, xml