<?php
/**
* Class of utility functions for XML manipulation.
*
* $array = xmlToArray($xmlString) Convert an XML string to an equivalent array.
* $xmlString = arrayToXml($array) Convert an array to an equivalent XML string.
*
* See the accompanying doc file for details of use.
* See the accompanying phpunit test file for examples of use.
*
* Please report any bugs to richard at roguewavelimited.com. If possible include
* a sample data file and description of the bug.
*
* If you have any suggestions on how to improve this class, let me know.
*
* You are free to use this class in any way you see fit.
*
* Richard Williams
* Rogue Wave Limited
* Jan 2, 2011
*/
class XmlHelper {
// Name to use as name of attribute array.
private $attributesArrayName = 'attributes';
// Case folding (uppercasing of names) is on by default (just like normal parser)
private $foldCase = true;
// Turn off values returned as arrays and ignore attributes.
// Attributes and value arrays are on by default.
private $noAttributes = false;
// If true, then leaing and trailing whitespace is removed from attributes and values.
// Off by default.
private $trimText = false;
// Vals returned by DOM parser.
private $vals;
// Name to use as name of value array.
private $valueArrayName = 'value';
/**
* Create XML from an array.
*
* @param <type> $array
* @param <type> $numericName Name to be used for numeric array indexes.
*/
function arrayToXml ($array) {
$dom = new DOMDocument();
$dom->encoding = "ISO-8859-1";
// $dom->encoding = "UTF-8";
$each = each($array);
$root = $each[0];
$array = $each[1];
$rootNode = $dom->appendChild($dom->createElement($root));
$this->putChildren($dom, $array, $rootNode);
return $dom->saveXML();
}
private function putChildren(DOMDocument &$dom, array $arr, DOMElement $node) {
/*
* Each node can be one of three types. 'value', an array indicating a sub-node
* 'and 'attributes' indicating an array of attributes.
*/
$arrayParent = null;
foreach($arr as $name => $content) {
if (strtoupper($name) == 'ATTRIBUTES') {
foreach($content as $n => $v) {
$node->setAttribute($n, $v);
}
} else if (strtoupper($name) == 'VALUE') {
$node->appendChild($dom->createTextNode($content));
} else {
// An integer index means that this starts a set
// of elements with the same name.
if (is_integer($name)) {
// Get the parent node and remove the integer element.
$child = $node;
if (is_null($arrayParent)) {
$arrayParent = $node->parentNode;
$arrayParent->removeChild($child);
} else {
$child = $dom->createElement($node->tagName, (is_array($content) ? null : htmlspecialchars($content)));
}
$arrayParent->appendChild($child);
} else {
$child = $dom->createElement($name, (is_array($content) ? null : htmlspecialchars($content)));
$node->appendChild($child);
}
if (is_array($content)) {
self::putChildren($dom, $content, $child);
}
}
}
}
function xmlToArray ($xmldata) {
$parser = xml_parser_create ('ISO-8859-1');
// $parser = xml_parser_create ('UTF-8');
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, $this->foldCase);
$vals = array();
if (!xml_parse_into_struct ($parser, $xmldata, $vals)) {
throw new Exception(sprintf("XML error: %s at line %d",
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser))); }
$folding = xml_parser_get_option($parser, XML_OPTION_CASE_FOLDING);
xml_parser_free ($parser);
$this->vals = $vals;
if ($folding) {
$this->valueArrayName = strtoupper($this->valueArrayName);
$this->attributesArrayName = strtoupper($this->attributesArrayName);
}
// Trim attributes and values if option set.
if ($this->trimText) {
foreach($vals as &$val) {
if (count($val['attributes']) > 0) {
foreach($val['attributes'] as $name => $att) {
if ($this->trimText) $val['attributes'][$name] = trim($att);
}
}
if (isset($val['value'])) {
if ($this->trimText) $val['value'] = trim($val['value']);
}
}
}
$i = 0;
$children = $this->getChildren ($vals, $i, $vals[$i]['type']);
// Add value and attributes to if present.
$valatt = $this->addAttributesAndValue($vals, 0);
if (! empty($valatt)) $children = array_merge($valatt, $children);
$result [$vals [$i]['tag']] = $children;
return $result;
}
/*
* Called recursively to parse the vals array.
*/
private function getChildren ($vals, &$i, $type) {
// If the type is 'complete' it means that there are no children of this element.
$children = array ();
if ($type != 'complete') {
while ($vals [++$i]['type'] != 'close') {
$type = $vals [$i]['type'];
$tag = $vals [$i]['tag'];
$valatt = $this->addAttributesAndValue($vals, $i);
// Check if we already have an element with this name and need to create an array
if (isset ($children [$tag])) {
$temp = array_keys ($children [$tag]);
if (is_string ($temp [0])) {
$a = $children [$tag];
unset ($children [$tag]);
$children [$tag][0] = $a;
}
$child = $this->getChildren($vals, $i, $type);
if (! empty($valatt)) $child = array_merge($valatt, $child);
$children [$tag][] = $child;
} else {
$children [$tag] = $this->getChildren ($vals, $i, $type);
// If a scalar is returned from addAttributeAndValue just set that as the return
// otherwise merge it with the existing children.
if (! is_array($valatt)) {
$children[$tag] = $valatt;
} else {
if (! empty($valatt)) $children[$tag] = array_merge($valatt, $children[$tag]);
}
}
}
}
return $children;
}
/**
* Add any attributes or values from parser to the output array.
*
* @param array $vals Parser vals.
* @param int $i Nesting level.
* @return array.
*/
private function addAttributesAndValue($vals, $i) {
$array = array();
if ($this->noAttributes) {
if (isset ($vals[$i]['value'])) $array = $vals [$i]['value'];
} else {
if (isset ($vals[$i]['value'])) $array = array($this->valueArrayName => $vals [$i]['value']);
if (isset ($vals[$i]['attributes']))
$array = array_merge($array, array($this->attributesArrayName => $vals[$i]['attributes']));
}
return $array;
}
/**
* Provides access to the parser's vals array.
* @return array of parser vals
*/
function getParserVals() {
return $this->vals;
}
/**
* If true then values are returned as raw values instead of as a 'VALUE' array.
* Also attributes are ignored.
* @param boolean $switch
*/
function setNoAttributes($switch) {
$this->noAttributes = $switch;
}
/**
* Set case folding (uppercasing) of all returned array names.
* This includes any value and attribute arrays.
* @param boolean $switch
*/
function setCaseFolding($switch) {
$this->foldCase = $switch;
}
/**
* If set, trims leading and trailing white space from attribute value and value text.
* @param boolean $switch
*/
function setTrimText($switch) {
$this->trimText = $switch;
}
}
?>
|