<?php
/**
* @version	$Id: xml_helper.php 12734 2009-10-20 19:28:11Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

	defined('FULL_PATH') or die('restricted access!');

	class kXMLHelper extends kHelper {

		var $RootElement = null;

		/**
		 * Enter description here...
		 *
		 * @var kXMLNode
		 */
		var $CurrentElement = null;

		var $Mode;

		/**
		 * Parses XML data specified and returns root node
		 *
		 * @param string $xml
		 * @return kXMLNode
		 */
		function &Parse($xml = null, $mode = XML_NO_TEXT_NODES)
		{
			$this->Mode = $mode;
			$this->Clear(); // in case if Parse method is called more then one time
			$xml_parser = xml_parser_create();
			xml_set_element_handler( $xml_parser, Array(&$this, 'startElement'), Array(&$this, 'endElement') );
			xml_set_character_data_handler( $xml_parser, Array(&$this, 'characterData') );
			if (!xml_parse($xml_parser, $xml, 1)) {
				$this->RootElement = new kXMLNode('ERROR', array('code'=>xml_get_error_code($xml_parser),'message'=>xml_error_string(xml_get_error_code($xml_parser))));
	    	    trigger_error(sprintf('XML error: %s at line %d',
					xml_error_string(xml_get_error_code($xml_parser)),
				xml_get_current_line_number($xml_parser)), E_USER_WARNING);
			}
			xml_parser_free($xml_parser);

			$root_copy = $this->RootElement;

			return $root_copy;
		}

		function ConvertHTMLEntities($s){
			//build first an assoc. array with the entities we want to match
			$table1 = get_html_translation_table(HTML_ENTITIES, ENT_QUOTES);

			$patterns = array();
			$replacements = array();
			//now build another assoc. array with the entities we want to replace (numeric entities)
			foreach ($table1 as $k=>$v){
			  $patterns[] = "/$v/";
		//	  $c = htmlentities($k,ENT_QUOTES,"UTF-8");
			  $replacements[] = "&#".ord($k).";";
			}

			//now perform a replacement using preg_replace
			//each matched value in array 1 will be replaced with the corresponding value in array 2
			$s = preg_replace($patterns,$replacements,$s);
			return $s;
		}

		function startElement(&$Parser, &$Elem, $Attrs)
		{
			$parent =& $this->CurrentElement; // 1. $parent is now reference to $this->CurrentElement
			$this->CurrentElement =& new kXMLNode($Elem, $Attrs); // 2. =& ensures, that new object won't be assigned to $parent as well (don't remove)
			if (!isset($this->RootElement) || is_null($this->RootElement)) {
				$this->RootElement =& $this->CurrentElement;
			}
			if (!is_null($parent)) {
				$parent->AddChild($this->CurrentElement);
			}
		}

		function characterData($Parser, $Line)
		{
			if ($this->Mode == XML_WITH_TEXT_NODES) {
				$text_node = new kXMLNode('_TEXT_');
				$text_node->AppendData($Line);
				$this->CurrentElement->AddChild( $text_node );
			}
			else {
				$this->CurrentElement->AppendData($Line);
			}
		}

		function endElement($Parser, $Elem)
		{
			if ($this->Mode == XML_WITH_TEXT_NODES) {
				/*if (count($this->CurrentElement->Children) == 1 && $this->CurrentElement->firstChild->Name == '_TEXT_') {
					$this->CurrentElement->Children = array();
				}*/
			}
			if ($this->CurrentElement->Parent != null) {
				$this->CurrentElement =& $this->CurrentElement->Parent;
			}
		}

		function Clear()
		{
			unset($this->RootElement);
			unset($this->CurrentElement);
		}
	}

	class kXMLNode {
		/**
		 * Name of this node
		 *
		 * @var string
		 */
		var $Name = null;

		/**
		 * Attributes of this node
		 *
		 * @var Array
		 */
		var $Attributes = array();

		/**
		 * List of node childnodes
		 *
		 * @var Array
		 */
		var $Children = array();

		/**
		 * Node content (usually text)
		 *
		 * @var string
		 */
		var $Data = null;

		/**
		 * First child of this node
		 *
		 * @var kXMLNode
		 */
		var $firstChild = null;

		/**
		 * Last child of this node
		 *
		 * @var kXMLNode
		 */
		var $lastChild = null;

		/**
		 * Parent node
		 *
		 * @var kXMLNode
		 */
		var $Parent = null;

		/**
		 * Node position relative to other nodes of it's parent
		 *
		 * @var int
		 */
		var $Position = 0;

		/**
		 * Node identifier
		 *
		 * @var int
		 */
		var $CRC = null;

		function kXMLNode($name, $attrs = array())
		{
			$this->Name = strtoupper($name);
			foreach ($attrs as $attr => $value) {
				$this->Attributes[strtoupper($attr)] = $value;
			}
			$this->CRC = crc32($this->Name.join(array_keys($this->Attributes)).join(array_values($this->Attributes)));
		}

		function SetParent(&$elem)
		{
			$this->Parent =& $elem;
		}

		/**
		 * Adds new child to current node
		 *
		 * @param kXMLNode $a_child
		 */
		function AddChild(&$a_child)
		{
			$node_count = count($this->Children);
			$a_child->Position = $node_count;

			if ($node_count == 0) {
				$this->firstChild =& $a_child;
				$this->lastChild =& $a_child;
			}
			else {
				$this->lastChild =& $a_child;
			}

			$this->Children[] =& $a_child;
			$a_child->SetParent($this);
		}

		/**
		 * Appends data to current node
		 *
		 * @param string $data
		 */
		function AppendData($data)
		{
			$this->Data .= $data;
		}

		/**
		 * Returns child node by given path
		 *
		 * @param string $path
		 * @return kXMLNode
		 */
		function &GetChild($path)
		{
			$entries = explode('/', strtoupper($path));
			$cur = array_shift($entries);
			if ($cur == $this->Name) $cur = array_shift($entries);
			if (!$cur) return $this;
			if (!isset($this->Children[$cur])) return false;
			$left = implode('/', $entries);
			if (!$left) return $this->Children[$cur];
			return $this->Children[$cur]->GetChild($left);
		}

		/**
		 * Returns node value by given path
		 *
		 * @param string $path
		 * @return string
		 */
		function GetChildValue($path)
		{
			$child =& $this->GetChild($path);
			if ($child !== false) {
				return $child->Data;
			}
		}

		/**
		 * Returns child node by given position among it siblings
		 *
		 * @param int $position
		 * @return kXMLNode
		 */
		function &GetChildByPosition($position)
		{
			if ($position < count($this->Children) ) {
				return $this->Children[$position];
			}
			else {
				$false = false;
				return $false;
			}
		}

		/**
		 * Recursively searches for child with given name under current node
		 *
		 * @param string $name
		 * @return kXMLNode
		 */
		function &FindChild($name)
		{
			$name = strtoupper($name);
			if ($this->Name == $name) return $this;
	//		if (isset($this->Children[$name])) return $this->Children[$name];
	//		$children = array_keys($this->Children);
			foreach ($this->Children as $elem)
			{
				$child =& $elem->FindChild($name);
				if ($child !== false)
				{
					return $child;
				}
			}
			$false = false;
			return $false;
		}

		/**
		 * Retruns value of given child or value of it's attribute
		 *
		 * @param string $name
		 * @param string $attr
		 * @return string
		 */
		function FindChildValue($name, $attr=null)
		{
			$child =& $this->FindChild($name);
			if ($child !== false) {
				if (isset($attr)) {
					return $child->Attributes[strtoupper($attr)];
				}
				return $child->Data;
			}
		}

		/**
		 * Returns next node to this, false in case of end list
		 *
		 * @return kXMLNode
		 */
		function &PrevSibling()
		{
			if (!is_null($this->Parent) && $this->Position > 0) {
				$pos = $this->Position - 1;
				do {
					$ret =& $this->Parent->GetChildByPosition($pos--);
				} while ($ret->Name == '_TEXT_' && $pos >= 0);
				if ($ret->Name == '_TEXT_') $ret = false;
				return $ret;
			}
			else {
				$false = false;
				return $false;
			}
		}

		/**
		 * Returns next node to this, false in case of end list
		 *
		 * @return kXMLNode
		 */
		function &NextSibling()
		{
			if (!is_null($this->Parent)) {
				$pos = $this->Position + 1;
				do {
					$ret =& $this->Parent->GetChildByPosition($pos++);
				} while ($pos < count($this->Parent->Children) && ($ret->Name == '_TEXT_'));

				if (is_object($ret) && ($ret->Name == '_TEXT_')) {
					$ret = false;
				}
				return $ret;
			}
			else {
				$false = false;
				return $false;
			}
		}
		/**
		 * Reconstructs XML of the node and subnodes
		 *
		 * $param bool $content_only
		 */
		function GetXML($content_only = false)
		{
			$xml = '';
			if (!$content_only) {
				$xml = '<'.$this->Name;
				if (count($this->Attributes)) {
					$xml .= ' ';
					$att_contents = array();
					foreach ($this->Attributes as $name => $value) {
						$att_contents[] = $name.'="'.$value.'"';
					}
					$xml .= implode(' ', $att_contents);
				}
				$xml .= '>';
			}
			$xml .= $this->Data;
			foreach ($this->Children as $node) {
				/* @var $node kXMLNode */

				$xml .= $node->GetXML($node->Name == '_TEXT_' ? true : false);
			}

			if (!$content_only) {
				$xml .= '</'.$this->Name.'>';
			}
			return $xml;
		}
	}