* Invision Power Services * IP.Board v3.4.9 * XML Handler: Rewritten for PHP5 * By Matt Mecham * Last Updated: $Date: 2012-07-27 11:43:25 -0400 (Fri, 27 Jul 2012) $ * * * @author $Author: mark $ * @copyright (c) 2001 - 2009 Invision Power Services, Inc. * @license http://www.invisionpower.com/company/standards.php#license * @package IP.Board * @subpackage Kernel * @link http://www.invisionpower.com * @since 25th February 2004 * @version $Revision: 11149 $ * * * Example Usage: * * * * * This is a descrption * Baked Beans * 103 * * * This is another descrption * Green Beans * 104 * * * * * Creating... * $xml = new classXML( 'utf-8' ); * $xml->newXMLDocument(); * * /* Create a root element * / * $xml->addElement( 'productlist', '', array( 'name' => 'myname', 'version' => '1.0' ) ); * /* Add a child.... * / * $xml->addElement( 'productgroup', 'productlist', array( 'name' => 'thisgroup' ) ); * $xml->addElementAsRecord( 'productgroup', * array( 'product', array( 'id' => '1.0' ) ), * array( 'description' => array( 'This is a description' ), * 'title' => array( 'Baked Beans' ), * 'room' => array( '103', array( 'store' => 1 ) ) * ) * ); * $xml->addElementAsRecord( 'productgroup', * array( 'product', array( 'id' => '2.0' ) ), * array( 'description' => array( 'This is another description' ), * 'title' => array( 'Green Beans' ), * 'room' => array( '104', array( 'store' => 2 ) ) * ) * ); * * $xmlData = $xml->fetchDocument(); * * /*Convering XML into an array * / * $xml->loadXML( $xmlData ); * * /* Grabbing specific data values from all 'products'... * / * foreach( $xml->fetchElements('product') as $products ) * { * print $xml->fetchItem( $products, 'title' ) . "\\"; * } * * /* Prints... * / * Baked Beans * Green Beans * * /* Print all array data - auto converts XML_TEXT_NODE and XML_CDATA_SECTION_NODE into #alltext for brevity * / * print_r( $xml->fetchXMLAsArray() ); * * /* Prints * / * Array * ( * [productlist] => Array * ( * [@attributes] => Array * ( * [name] => myname * [version] => 1.0 * ) * [productgroup] => Array * ( * [@attributes] => Array * ( * [name] => thisgroup * ) * [product] => Array * ( * [0] => Array * ( * [@attributes] => Array * ( * [id] => 1.0 * ) * [description] => Array * ( * [#alltext] => This is a description * ) * [title] => Array * ( * [#alltext] => Baked Beans * ) * [room] => Array * ( * [@attributes] => Array * ( * [store] => 1.0 * ) * [#alltext] => 103 * ) * ) * [1] => Array * ( * [@attributes] => Array * ( * [id] => 2.0 * ) * [description] => Array * ( * [#alltext] => This is another description * ) * [title] => Array * ( * [#alltext] => Green Beans * ) * [room] => Array * ( * [@attributes] => Array * ( * [store] => 1.0 * ) * [#alltext] => 104 * ) * ) * ) * ) * ) * ) * * * */ class classXML { /** * Document character set * * @var string */ protected $_docCharSet = 'utf-8'; /** * Current document object * * @var object */ protected $_dom; /** * Array of DOM objects * * @var array */ protected $_domObjects = array(); /** * XML array * * @var array */ protected $_xmlArray = array(); /** * Conversion class * * @var object */ protected static $classConvertCharset; /** * Constructor * * @param string Character Set * @return @e void */ public function __construct( $charSet ) { $this->_docCharSet = strtolower( $charSet ); } /** * Create new document * * @return @e void */ public function newXMLDocument() { $this->_dom = new DOMDocument( '1.0', 'utf-8' ); } /** * Fetch the document * * @return @e string */ public function fetchDocument() { $this->_dom->formatOutput = TRUE; return $this->_dom->saveXML(); } /** * Add element into the document * * @param string Name of tag to create * @param string [Name of parent tag (optional)] * @param array [Attributes] * @param string [Namespace URI] * @return @e void */ public function addElement( $tag, $parentTag='', $attributes=array(), $namespaceUri='' ) { $this->_domObjects[ $tag ] = $this->_node( $parentTag )->appendChild( new DOMElement( $tag, '', $namespaceUri ) ); $this->addAttributes( $tag, $attributes ); } /** * Add element into the document as a record row * You can pass $tag as either a string or an array * * $xml->addElementAsRecord( 'parentTag', 'myTag', $data ); * $xml->addElementAsRecord( 'parentTag', array( 'myTag', array( 'attr' => 'value' ) ), $data ); * * @param string Name of parent tag * @param mixed Tag wrapper * @param array Array of data to add * @param string [Namespace URI] * @return @e void */ public function addElementAsRecord( $parentTag, $tag, $data, $namespaceUri='' ) { /* A little set up if you please... */ $_tag = $tag; $_tag_attr = array(); if ( is_array( $tag ) ) { $_tag = $tag[0]; $_tag_attr = $tag[1]; } $record = $this->_node( $parentTag )->appendChild( new DOMElement( $_tag, ( is_array( $data ) ? NULL : $data ), $namespaceUri ) ); if ( is_array( $_tag_attr ) AND count( $_tag_attr ) ) { foreach( $_tag_attr as $k => $v ) { $record->appendChild( new DOMAttr( $k, $v ) ); } } /* Now to add the data */ if ( is_array( $data ) AND count( $data ) ) { foreach( $data as $rowTag => $rowData ) { /* You can pass an array.. or not if you don't need attributes */ if ( ! is_array( $rowData ) ) { $rowData = array( 0 => $rowData ); } if ( preg_match( "/['\"\[\]<>&]/", $rowData[0] ) ) { $_child = $record->appendChild( new DOMElement( $rowTag ) ); $_child->appendChild( new DOMCDATASection( $this->_inputToXml( $rowData[0] ) ) ); } else { $_child = $record->appendChild( new DOMElement( $rowTag, $this->_inputToXml( $rowData[0] ) ) ); } if ( $rowData[1] ) { foreach( $rowData[1] as $_k => $_v ) { $_child->appendChild( new DOMAttr( $_k, $_v ) ); } } unset( $_child ); } } } /** * Add attributes to a node * * @param string Name of tag * @param array Array of attributes in key => value format * @return @e void */ public function addAttributes( $tag, $data ) { if ( is_array( $data ) AND count( $data ) ) { foreach( $data as $k => $v ) { $this->_node( $tag )->appendChild( new DOMAttr( $k, $v ) ); } } } /** * Load a document from a file * * @param string File name * @return @e void */ public function load( $filename ) { $this->_dom = new DOMDocument; $this->_dom->load( $filename ); } /** * Load a document from a string * * @param string XML Data * @return @e bool */ public function loadXML( $xmlData ) { $this->_dom = new DOMDocument; if( defined('LIBXML_PARSEHUGE') ) { return @$this->_dom->loadXML( $xmlData, LIBXML_PARSEHUGE ); } else { return @$this->_dom->loadXML( $xmlData ); } } /** * Wrapper function: Fetch elements based on tag name * * @param string Tag Name to fetch from the DOM tree * @param object Node to start from * @return @e array */ public function fetchElements( $tag, $node=null ) { $start = $node ? $node : $this->_dom; $_elements = $start->getElementsByTagName( $tag ); return ( $_elements->length ) ? $_elements : array(); } /** * Wrapper function: Fetch all items within a parent tag * * @param object DOM object as returned from getElementsByTagName * @param array array of node names to skip * @return @e array */ public function fetchElementsFromRecord( $dom, $skip=array() ) { $array = array(); foreach( $dom->childNodes as $node ) { if ( $node->nodeType == XML_ELEMENT_NODE ) { if ( is_array( $skip ) ) { if ( in_array( $node->nodeName, $skip ) ) { continue; } } $array[ $node->nodeName ] = $this->_xmlToOutput( $node->nodeValue ); } } return $array; } /** * Wrapper function: Fetch items from an element node * * @param object DOM object as returned from getElementsByTagName * @param string [Optional: Tag name if the DOM is a parent] * @return @e string */ public function fetchItem( $dom, $tag='' ) { if ( $tag ) { $_child = $dom->getElementsByTagName( $tag ); return $this->_xmlToOutput( $_child->item(0)->firstChild->nodeValue ); } else { return $this->_xmlToOutput( $dom->nodeValue ); } } /** * Wrapper function: Fetch attributes from an element node's item * * @param object DOM object as returned from getElementsByTagName * @param string Attribute name required... * @param string [Optional: Tag name if the DOM is a parent] * @return @e string */ public function fetchAttribute( $dom, $attribute, $tag='' ) { if ( $tag ) { $_child = $dom->getElementsByTagName( $tag ); return $_child->item(0)->getAttribute( $attribute ); } else { return $dom->getAttribute( $attribute ); } } /** * Wrapper function: Fetch all attributes from an element node's item * * @param object DOM object as returned from getElementsByTagName * @param string Tag name to fetch attribute from * @return @e array */ public function fetchAttributesAsArray( $dom, $tag ) { $attrs = array(); $_child = $dom->getElementsByTagName( $tag ); $attributes = $_child->item(0)->attributes; foreach( $attributes as $val ) { $attrs[ $val->nodeName ] = $val->nodeValue; } return $attrs; } /** * Fetch entire DOM tree into a single array * * @return @e array */ public function fetchXMLAsArray() { return $this->_fetchXMLAsArray( $this->_dom ); } /** * Internal function to recurse through and collect nodes and data * * @param DOM object Node element * @return @e array */ protected function _fetchXMLAsArray( $node ) { $_xmlArray = array(); if ( $node->nodeType == XML_TEXT_NODE ) { $_xmlArray = $this->_xmlToOutput( $node->nodeValue ); } else if ( $node->nodeType == XML_CDATA_SECTION_NODE ) { $_xmlArray = $this->_xmlToOutput( $node->nodeValue ); } else { if ( $node->hasAttributes() ) { $attributes = $node->attributes; if ( ! is_null( $attributes ) ) { foreach( $attributes as $index => $attr ) { $_xmlArray['@attributes'][ $attr->name ] = $attr->value; } } } if ( $node->hasChildNodes() ) { $children = $node->childNodes; $occurance = array(); foreach( $children as $nc) { if ( $nc->nodeName != '#text' AND $nc->nodeName != '#cdata-section' ) { $occurance[ $nc->nodeName ] = isset($occurance[ $nc->nodeName ]) ? $occurance[ $nc->nodeName ] + 1 : 1; } } for( $i = 0 ; $i < $children->length ; $i++ ) { $child = $children->item( $i ); $_name = $child->nodeName; if ( $child->nodeName == '#text' OR $child->nodeName == '#cdata-section' ) { $_name = '#alltext'; } if ( isset($occurance[ $child->nodeName ]) AND $occurance[ $child->nodeName ] > 1 ) { $_xmlArray[ $_name ][] = $this->_fetchXMLAsArray( $child ); } else { $_xmlArray[ $_name ] = $this->_fetchXMLAsArray( $child ); } } } } return $_xmlArray; } /** * Encode CDATA XML attribute (Make safe for transport) * * @param string Raw data * @return @e string */ protected function _xmlConvertSafecdata( $v ) { $v = str_replace( "" , "|#^#]>" , $v ); return $v; } /** * Decode CDATA XML attribute (Make safe for transport) * * @param string Raw data * @return @e string */ protected function _xmlUnconvertSafecdata( $v ) { $v = str_replace( "" , "]]>" , $v ); return $v; } /** * Return a tag object * * @param string Name of tag * @return @e object */ protected function _node( $tag ) { if ( isset($this->_domObjects[ $tag ]) ) { return $this->_domObjects[ $tag ]; } else { return $this->_dom; } } /** * Convert from native to UTF-8 for saving XML * * @param string Input Text * @return @e string */ protected function _inputToXml( $text ) { /* Do we need to make safe on CDATA? */ if ( preg_match( "/['\"\[\]<>&]/", $text ) ) { $text = $this->_xmlConvertSafecdata( $text ); } /* Using UTF-8 */ if ( $this->_docCharSet == 'utf-8' ) { return $text; } /* Are we using the most common ISO-8559-1... */ else if ( $this->_docCharSet == 'iso-8859-1' ) { return utf8_encode( $text ); } else { return $this->_convertCharsets( $text, $this->_docCharSet, 'utf-8' ); } } /** * Convert from UTF-8 to native for saving XML * * @param string Input Text * @return @e string */ protected function _xmlToOutput( $text ) { /* Unconvert cdata */ $text = $this->_xmlUnconvertSafecdata( $text ); /* Using UTF-8 */ if ( $this->_docCharSet == 'utf-8' ) { return $text; } /* Are we using the most common ISO-8559-1... */ else if ( $this->_docCharSet == 'iso-8859-1' ) { return utf8_decode( $text ); } else { return $this->_convertCharsets( $text, 'utf-8', $this->_docCharSet ); } } /** * Convert a string between charsets. XML will always be UTF-8 * * @param string Input String * @param string Current char set * @param string Destination char set * @return @e string * @todo [Future] If an error is set in classConvertCharset, show it or log it somehow */ protected function _convertCharsets( $text, $original_cset, $destination_cset="UTF-8" ) { $original_cset = strtolower($original_cset); $destination_cset = strtolower( $destination_cset ); $t = $text; //----------------------------------------- // Not the same? //----------------------------------------- if ( $destination_cset == $original_cset ) { return $t; } if ( ! is_object( self::$classConvertCharset ) ) { require_once( dirname(__FILE__) . '/classConvertCharset.php' );/*noLibHook*/ self::$classConvertCharset = new classConvertCharset(); //----------------------------------------- // Ok, mb functions only support limited number // of charsets, so if mb functions are enabled // but using e.g. windows-1250, no conversion // ends up happening. Let's force internal. //----------------------------------------- //if ( function_exists( 'mb_convert_encoding' ) ) //{ // self::$classConvertCharset->method = 'mb'; //} //else if ( function_exists( 'iconv' ) ) //{ // self::$classConvertCharset->method = 'iconv'; //} //else if ( function_exists( 'recode_string' ) ) //{ // self::$classConvertCharset->method = 'recode'; //} //else //{ self::$classConvertCharset->method = 'internal'; //} } $text = self::$classConvertCharset->convertEncoding( $text, $original_cset, $destination_cset ); return $text ? $text : $t; } }