<?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; 
    } 
} 
?> 
 
 |