Your IP : 18.191.171.86


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/classes/general/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/classes/general/xml.php

<?php
/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage main
 * @copyright 2001-2023 Bitrix
 */

use Bitrix\Main\Text\Encoding;

class CDataXMLNode
{
	var $name;
	var $content;
	/** @var CDataXMLNode[] */
	var $children = [];
	/** @var CDataXMLNode[] */
	var $attributes;
	var $_parent;

	public function name()
	{
		return $this->name;
	}

	public function children()
	{
		return $this->children;
	}

	public function textContent()
	{
		return $this->content;
	}

	public function getAttribute($attribute)
	{
		if (is_array($this->attributes))
		{
			foreach ($this->attributes as $anode)
			{
				if ($anode->name == $attribute)
				{
					return $anode->content;
				}
			}
		}
		return "";
	}

	public function getAttributes()
	{
		return $this->attributes;
	}

	public function namespaceURI()
	{
		return $this->getAttribute("xmlns");
	}

	/**
	 * @param $tagname
	 * @return CDataXMLNode[]
	 */
	public function elementsByName($tagname)
	{
		$result = array();

		if ($this->name == $tagname)
		{
			$result[] = $this;
		}

		if (is_array($this->children))
		{
			foreach ($this->children as $node)
			{
				$more = $node->elementsByName($tagname);
				if (is_array($more))
				{
					foreach ($more as $mnode)
					{
						$result[] = $mnode;
					}
				}
			}
		}
		return $result;
	}

	function _SaveDataType_OnDecode(&$result, $name, $value)
	{
		if (isset($result[$name]))
		{
			$i = 1;
			while (isset($result[$i.":".$name]))
				$i++;
			$result[$i.":".$name] = $value;
			return "indexed";
		}
		else
		{
			$result[$name] = $value;
			return "common";
		}
	}

	function decodeDataTypes($attrAsNodeDecode = false)
	{
		$result = array();

		if (!$this->children)
		{
			$this->_SaveDataType_OnDecode($result, $this->name(), $this->textContent());
		}
		else
		{
			foreach ($this->children() as $child)
			{
				$cheese = $child->children();
				if (!$cheese or !count($cheese))
				{
					$this->_SaveDataType_OnDecode($result, $child->name(), $child->textContent());
				}
				else
				{
					$cheresult = $child->decodeDataTypes();
					if (is_array($cheresult))
						$this->_SaveDataType_OnDecode($result, $child->name(), $cheresult);
				}
			}
		}

		if ($attrAsNodeDecode)
		{
			foreach ($this->getAttributes() as $child)
			{
				$this->_SaveDataType_OnDecode($result, $child->name(), $child->textContent());
			}
		}

		return $result;
	}

	function __toString()
	{
		switch ($this->name)
		{
			case "cdata-section":
				$ret = "<![CDATA[";
				$ret .= $this->content;
				$ret .= "]]>";
				break;

			default:
				$isOneLiner = false;

				if (empty($this->children) && $this->content == '')
					$isOneLiner = true;

				$attrStr = "";

				if (is_array($this->attributes))
				{
					foreach ($this->attributes as $attr)
					{
						$attrStr .= " ".$attr->name."=\"".CDataXML::xmlspecialchars($attr->content)."\" ";
					}
				}

				if ($isOneLiner)
					$oneLinerEnd = " /";
				else
					$oneLinerEnd = "";

				$ret = "<".$this->name.$attrStr.$oneLinerEnd.">";

				if (is_array($this->children))
				{
					foreach ($this->children as $child)
					{
						$ret .= $child->__toString();
					}
				}

				if (!$isOneLiner)
				{
					if ($this->content <> '')
						$ret .= CDataXML::xmlspecialchars($this->content);

					$ret .= "</".$this->name.">";
				}

				break;
		}

		return $ret;
	}

	public function __toArray()
	{
		$retHash = array(
			"@" => array(),
		);

		if (is_array($this->attributes))
		{
			foreach ($this->attributes as $attr)
			{
				$retHash["@"][$attr->name] = $attr->content;
			}
		}

		if ($this->content != "")
		{
			$retHash["#"] = $this->content;
		}
		elseif (!empty($this->children))
		{
			$ar = array();
			foreach ($this->children as $child)
			{
				$ar[$child->name][] = $child->__toArray();
			}
			$retHash["#"] = $ar;
		}
		else
		{
			$retHash["#"] = "";
		}

		return $retHash;
	}

	public function toSimpleArray()
	{
		if (!empty($this->children))
		{
			$ar = [];
			foreach ($this->children as $child)
			{
				$ar[$child->name][] = $child->toSimpleArray();
			}

			// simplify the array
			foreach ($ar as $name => $value)
			{
				if (count($value) == 1)
				{
					$ar[$name] = $value[0];
				}
			}
			return $ar;
		}

		return $this->content;
	}
}

class CDataXMLDocument
{
	var $version = '';
	var $encoding = '';

	/** @var CDataXMLNode[] */
	var $children;
	var $root;

	function elementsByName($tagname)
	{
		$result = array();
		if (is_array($this->children))
		{
			foreach ($this->children as $node)
			{
				$more = $node->elementsByName($tagname);
				if (is_array($more))
				{
					foreach ($more as $mnode)
					{
						$result[] = $mnode;
					}
				}
			}
		}
		return $result;
	}

	public static function encodeDataTypes($name, $value)
	{
		static $Xsd = array(
			"string"=>"string", "bool"=>"boolean", "boolean"=>"boolean",
			"int"=>"integer", "integer"=>"integer", "double"=>"double", "float"=>"float", "number"=>"float",
			"array"=>"anyType", "resource"=>"anyType",
			"mixed"=>"anyType", "unknown_type"=>"anyType", "anyType"=>"anyType"
		);

		$node = new CDataXMLNode();
		$node->name = $name;

		if (is_object($value))
		{
			$ovars = get_object_vars($value);
			foreach ($ovars as $pn => $pv)
			{
				$decode = static::encodeDataTypes($pn, $pv);
				if ($decode)
				{
					$node->children[] = $decode;
				}
			}
		}
		else if (is_array($value))
		{
			foreach ($value as $pn => $pv)
			{
				$decode = static::encodeDataTypes( $pn, $pv);
				if ($decode)
				{
					$node->children[] = $decode;
				}
			}
		}
		else
		{
			if (isset($Xsd[gettype($value)]))
			{
				$node->content = $value;
			}
		}
		return $node;
	}

	/* Returns an XML string of the DOM document */
	public function __toString()
	{
		$ret = "<"."?xml";
		if ($this->version <> '')
		{
			$ret .= " version=\"".$this->version."\"";
		}
		if ($this->encoding <> '')
		{
			$ret .= " encoding=\"".$this->encoding."\"";
		}
		$ret .= "?".">";

		if (is_array($this->children))
		{
			foreach ($this->children as $child)
			{
				$ret .= $child->__toString();
			}
		}

		return $ret;
	}

	/* Returns an array of the DOM document */
	public function __toArray()
	{
		$arRetArray = array();

		if (is_array($this->children))
		{
			foreach ($this->children as $child)
			{
				$arRetArray[$child->name] = $child->__toArray();
			}
		}

		return $arRetArray;
	}

	/**
	 * Returns a simplified array without attributes, with only nodes content.
	 * @return array
	 */
	public function toSimpleArray(): array
	{
		$arRetArray = [];

		if (is_array($this->children))
		{
			foreach ($this->children as $child)
			{
				$arRetArray[$child->name] = $child->toSimpleArray();
			}
		}

		return $arRetArray;
	}
}

class CDataXML
{
	/** @var CDataXMLDocument */
	var $tree;
	var $TrimWhiteSpace;

	var $delete_ns = true;

	public function __construct($TrimWhiteSpace = true)
	{
		$this->TrimWhiteSpace = (bool)$TrimWhiteSpace;
		$this->tree = false;
	}

	public function Load($file)
	{
		$this->tree = false;

		if (file_exists($file))
		{
			$content = file_get_contents($file);

			if (defined('SITE_CHARSET'))
			{
				$charset = (defined("BX_DEFAULT_CHARSET")? BX_DEFAULT_CHARSET : "windows-1251");
				if (preg_match("/<"."\\?XML[^>]+encoding=[\"']([^>\"']+)[\"'][^>]*\\?".">/i", $content, $matches))
				{
					$charset = trim($matches[1]);
				}
				$content = Encoding::convertEncoding($content, $charset, SITE_CHARSET);
			}
			$this->tree = $this->__parse($content);
			return $this->tree !== false;
		}

		return false;
	}

	public function LoadString($text)
	{
		$this->tree = false;

		if ($text <> '')
		{
			$this->tree = $this->__parse($text);
			return ($this->tree !== false);
		}

		return false;
	}

	public function GetTree()
	{
		return $this->tree;
	}

	public function GetArray()
	{
		if (!is_object($this->tree))
		{
			return false;
		}
		return $this->tree->__toArray();
	}

	public function GetString()
	{
		if (!is_object($this->tree))
		{
			return false;
		}
		return $this->tree->__toString();
	}

	public function SelectNodes($strNode)
	{
		if (!is_object($this->tree))
		{
			return false;
		}

		$result = $this->tree;

		$tmp = explode("/", $strNode);
		$tmpCount = count($tmp);
		for ($i = 1; $i < $tmpCount; $i++)
		{
			if ($tmp[$i] != "")
			{
				if (!is_array($result->children))
					return false;

				$bFound = false;
				for ($j = 0, $c = count($result->children); $j < $c; $j++)
				{
					if ($result->children[$j]->name==$tmp[$i])
					{
						$result = $result->children[$j];
						$bFound = true;
						break;
					}
				}

				if (!$bFound)
				{
					return false;
				}
			}
		}

		return $result;
	}

	public static function xmlspecialchars($str)
	{
		static $search = array("&","<",">","\"","'");
		static $replace = array("&amp;","&lt;","&gt;","&quot;","&apos;");
		return str_replace($search, $replace, $str);
	}

	public static function xmlspecialcharsback($str)
	{
		static $search = array("&lt;","&gt;","&quot;","&apos;","&amp;");
		static $replace = array("<",">","\"","'","&");
		return str_replace($search, $replace, $str);
	}

	/**
	 * Will return an DOM object tree from the well-formed XML.
	 *
	 * @param string $strXMLText
	 * @return CDataXMLDocument | false
	 */
	protected function __parse(&$strXMLText)
	{
		static $search = array("&gt;","&lt;","&apos;","&quot;","&amp;");
		static $replace = array(">","<","'",'"',"&");

		$oXMLDocument = new CDataXMLDocument();

		// strip comments
		$strXMLText = CDataXML::__stripComments($strXMLText);

		// stip the !doctype
		// The DOCTYPE declaration can consist of an internal DTD in square brackets
		$cnt = 0;
		$strXMLText = preg_replace("%<!DOCTYPE[^\\[>]*\\[.*?]>%is", "", $strXMLText, -1, $cnt);
		if ($cnt == 0)
		{
			$strXMLText = preg_replace("%<!DOCTYPE[^>]*>%i", "", $strXMLText);
		}

		// get document version and encoding from header
		preg_match_all("#<\\?(.*?)\\?>#i", $strXMLText, $arXMLHeader_tmp);
		foreach ($arXMLHeader_tmp[0] as $strXMLHeader_tmp)
		{
			preg_match_all("/([a-zA-Z:]+=\".*?\")/i", $strXMLHeader_tmp, $arXMLParam_tmp);
			foreach ($arXMLParam_tmp[0] as $strXMLParam_tmp)
			{
				if ($strXMLParam_tmp <> '')
				{
					$arXMLAttribute_tmp = explode("=\"", $strXMLParam_tmp);
					if ($arXMLAttribute_tmp[0] == "version")
						$oXMLDocument->version = mb_substr($arXMLAttribute_tmp[1], 0, mb_strlen($arXMLAttribute_tmp[1]) - 1);
					elseif ($arXMLAttribute_tmp[0] == "encoding")
						$oXMLDocument->encoding = mb_substr($arXMLAttribute_tmp[1], 0, mb_strlen($arXMLAttribute_tmp[1]) - 1);
				}
			}
		}

		// strip header
		$strXMLText = preg_replace("#<\\?.*?\\?>#", "", $strXMLText);

		$oXMLDocument->root = &$oXMLDocument->children;

		/** @var CDataXMLNode $currentNode */
		$currentNode = &$oXMLDocument;

		$tok = strtok($strXMLText, "<");
		$arTag = explode(">", $tok);
		if (count($arTag) < 2)
		{
			//There was whitespace before <, so make another try
			$tok = strtok("<");
			$arTag = explode(">", $tok);
			if (count($arTag) < 2)
			{
				//It's a broken XML
				return false;
			}
		}

		while ($tok !== false)
		{
			$tagName = $arTag[0];
			$tagContent = $arTag[1];

			// find tag name with attributes
			// check if it's an endtag </tagname>
			if ($tagName[0] == "/")
			{
				$tagName = mb_substr($tagName, 1);
				// strip out namespace; nameSpace:Name
				if ($this->delete_ns)
				{
					$colonPos = mb_strpos($tagName, ":");

					if ($colonPos > 0)
						$tagName = mb_substr($tagName, $colonPos + 1);
				}

				if ($currentNode->name != $tagName)
				{
					// Error parsing XML, unmatched tags $tagName
					return false;
				}

				$currentNode = $currentNode->_parent;

				// convert special chars
				if ((!$this->TrimWhiteSpace) || (trim($tagContent) != ""))
					$currentNode->content = str_replace($search, $replace, $tagContent);
			}
			elseif (strncmp($tagName, "![CDATA[", 8) === 0)
			{
				//because cdata may contain > and < chars
				//it is special processing needed
				$cdata = "";
				for($i = 0, $c = count($arTag); $i < $c; $i++)
				{
					$cdata .= $arTag[$i].">";
					if (mb_substr($cdata, -3) == "]]>")
					{
						$tagContent = $arTag[$i+1];
						break;
					}
				}

				if (mb_substr($cdata, -3) != "]]>")
				{
					$cdata = mb_substr($cdata, 0, -1)."<";
					do
					{
						$tok = strtok(">");//unfortunatly strtok eats > followed by >
						$cdata .= $tok.">";
						//util end of string or end of cdata found
					}
					while ($tok !== false && mb_substr($tok, -2) != "]]");
					//$tagName = substr($tagName, 0, -1);
				}

				$cdataSection = mb_substr($cdata, 8, -3);

				// new CDATA node
				$subNode = new CDataXMLNode();
				$subNode->name = "cdata-section";
				$subNode->content = $cdataSection;

				$currentNode->children[] = $subNode;
				$currentNode->content .= $subNode->content;

				// convert special chars
				if ((!$this->TrimWhiteSpace) || (trim($tagContent) != ""))
					$currentNode->content = str_replace($search, $replace, $tagContent);
			}
			else
			{
				// normal start tag
				if (preg_match('/^(\S+)(.*)$/s', $tagName, $match))
				{
					$justName = $match[1];
					$attributePart = $match[2];
				}
				else
				{
					$justName = $tagName;
					$attributePart = '';
				}

				// strip out namespace; nameSpace:Name
				if ($this->delete_ns)
				{
					$colonPos = mb_strpos($justName, ":");

					if ($colonPos > 0)
						$justName = mb_substr($justName, $colonPos + 1);
				}

				// remove trailing / from the name if exists
				$justName = rtrim($justName, "/");

				$subNode = new CDataXMLNode();
				$subNode->_parent = $currentNode;
				$subNode->name = $justName;

				// find attributes
				if ($attributePart)
				{
					// attributes
					$attr = $this->__parseAttributes($attributePart);

					if ($attr)
					{
						$subNode->attributes = $attr;
					}
				}

				// convert special chars
				if ((!$this->TrimWhiteSpace) || (trim($tagContent) != ""))
				{
					$subNode->content = str_replace($search, $replace, $tagContent);
				}

				$currentNode->children[] = $subNode;

				if (mb_substr($tagName, -1) != "/")
				{
					$currentNode = $subNode;
				}
			}

			//Next iteration
			$tok = strtok("<");
			$arTag = explode(">", $tok);
			//There was whitespace before < just after CDATA section, so make another try
			if (count($arTag) < 2 && (strncmp($tagName, "![CDATA[", 8) === 0))
			{
				$currentNode->content .= $arTag[0];

				// convert special chars
				if ((!$this->TrimWhiteSpace) || (trim($tagContent) != ""))
					$currentNode->content = str_replace($search, $replace, $tagContent);

				$tok = strtok("<");
				$arTag = explode(">", $tok);
			}
		}
		return $oXMLDocument;
	}

	protected static function __stripComments($str)
	{
		$str = preg_replace("#<!--.*?-->#s", "", $str);
		return $str;
	}

	/* Parses the attributes. Returns false if no attributes in the supplied string is found */
	protected function __parseAttributes($attributeString)
	{
		$ret = false;

		preg_match_all("/(\\S+)\\s*=\\s*([\"'])(.*?)\\2/s".BX_UTF_PCRE_MODIFIER, $attributeString, $attributeArray);

		foreach ($attributeArray[0] as $i => $attributePart)
		{
			$attributePart = trim($attributePart);
			if ($attributePart != "" && $attributePart != "/")
			{
				$attributeName = $attributeArray[1][$i];

				// strip out namespace; nameSpace:Name
				if ($this->delete_ns)
				{
					$colonPos = mb_strpos($attributeName, ":");

					if ($colonPos > 0)
					{
						// exclusion: xmlns attribute is xmlns:nameSpace
						if ($colonPos == 5 && (mb_substr($attributeName, 0, $colonPos) == 'xmlns'))
						{
							$attributeName = 'xmlns';
						}
						else
						{
							$attributeName = mb_substr($attributeName, $colonPos + 1);
						}
					}
				}
				$attributeValue = $attributeArray[3][$i];

				$attrNode = new CDataXMLNode();
				$attrNode->name = $attributeName;
				$attrNode->content = CDataXML::xmlspecialcharsback($attributeValue);

				$ret[] = $attrNode;
			}
		}
		return $ret;
	}
}
/*
Usage:

class OrderLoader
{
	var $errors = array();

	function elementHandler($path, $attr)
	{
		AddMessage2Log(print_r(array($path, $attr), true));
	}

	function nodeHandler(CDataXML $xmlObject)
	{
		AddMessage2Log(print_r($xmlObject, true));
	}
}

$position = false;
$loader = new OrderLoader;

while(true) //this while is cross hit emulation
{
	$o = new CXMLFileStream;
	$o->registerElementHandler("/������������������", array($loader, "elementHandler"));
	$o->registerNodeHandler("/������������������/�������/�����/�����", array($loader, "nodeHandler"));
	$o->setPosition($position);

	if ($o->openFile($_SERVER["DOCUMENT_ROOT"]."/upload/081_books_books-books_ru.xml"))
	{
		while($o->findNext())
		{
			//if (time() > $endTime)
			break;
		}

		if ($o->endOfFile())
		{
			break;
		}
		else
		{
			$position = $o->getPosition();
		}
	}
}
*/
class CXMLFileStream
{
	private $fileCharset = false;
	private $filePosition = 0;
	private $xmlPosition = "";
	private $nodeHandlers = array();
	private $elementHandlers = array();
	private $endNodes = array();
	private $fileHandler = null;

	private $eof = false;
	private $readSize = 1024;
	private $buf = "";
	private $bufPosition = 0;
	private $bufLen = 0;
	private $positionStack = array();
	private $elementStack = array();
	/**
	 * Registers a handler function which will be called on xml parsed path with CDataXML object as a parameter
	 *
	 * @param string $nodePath
	 * @param mixed $callableHandler
	 * @return void
	 *
	 */
	public function registerNodeHandler($nodePath, $callableHandler)
	{
		if (is_callable($callableHandler))
		{
			if (!isset($this->nodeHandlers[$nodePath]))
				$this->nodeHandlers[$nodePath] = array();
			$this->nodeHandlers[$nodePath][] = $callableHandler;

			$pathComponents = explode("/", $nodePath);
			$this->endNodes[end($pathComponents)] = true;
		}
	}
	/**
	 * Registers a handler function which will be called on xml parsed path with path and attributes
	 *
	 * @param string $nodePath
	 * @param mixed $callableHandler
	 * @return void
	 *
	 */
	public function registerElementHandler($nodePath, $callableHandler)
	{
		if (is_callable($callableHandler))
		{
			if (!isset($this->elementHandlers[$nodePath]))
				$this->elementHandlers[$nodePath] = array();
			$this->elementHandlers[$nodePath][] = $callableHandler;

			$pathComponents = explode("/", $nodePath);
			$this->endNodes[end($pathComponents)] = true;
		}
	}
	/**
	 * Opens file by its absolute path. Returns true on success.
	 *
	 * @param string $filePath
	 * @return bool
	 *
	 */
	public function openFile($filePath)
	{
		$this->fileHandler = null;

		$io = CBXVirtualIo::getInstance();
		$file = $io->getFile($filePath);
		$this->fileHandler = $file->open("rb");
		if (is_resource($this->fileHandler))
		{
			if ($this->filePosition > 0)
				fseek($this->fileHandler, $this->filePosition);

			$this->elementStack = array();
			$this->positionStack = array();
			foreach (explode("/", $this->xmlPosition) as $pathPart)
			{
				@list($elementPosition, $elementName) = explode("@", $pathPart, 2);
				$this->elementStack[] = $elementName;
				$this->positionStack[] = $elementPosition;
			}

			return true;
		}
		else
		{
			return false;
		}
	}
	/**
	 * Returns true when end of the file is reached.
	 *
	 * @return bool
	 *
	 */
	public function endOfFile()
	{
		if ($this->fileHandler === null)
			return true;
		else
			return $this->eof;
	}
	/**
	 * Returns current position state needed to continue file parsing process on the next hit.
	 *
	 * @return array[int]string
	 *
	 */
	public function getPosition()
	{
		$this->xmlPosition = array();
		foreach ($this->elementStack as $i => $elementName)
		{
			$this->xmlPosition[] = $this->positionStack[$i]."@".$elementName;
		}
		$this->xmlPosition = implode("/", $this->xmlPosition);

		return array(
			$this->fileCharset,
			$this->filePosition,
			$this->xmlPosition,
		);
	}
	/**
	 * Sets the position state returned by getPosition method.
	 *
	 * @param string[][] $position
	 * @return void
	 *
	 */
	public function setPosition($position)
	{
		if (is_array($position))
		{
			if (isset($position[0]))
				$this->fileCharset = $position[0];
			if (isset($position[1]))
				$this->filePosition = $position[1];
			if (isset($position[2]))
				$this->xmlPosition = $position[2];
		}
	}
	/**
	 * Processes file futher. Returns true when there is more work to do. False on the end of file.
	 *
	 * @return bool
	 *
	 */
	public function findNext()
	{
		$cs = $this->fileCharset;

		if ($this->fileHandler === null)
			return false;

		$this->eof = false;
		while (($xmlChunk = $this->getXmlChunk()) !== false)
		{
			$origChunk = $xmlChunk;
			if ($cs)
			{
				$xmlChunk = Encoding::convertEncoding($origChunk, $cs, LANG_CHARSET);
			}

			if ($xmlChunk[0] == "/")
			{
				$this->endElement();
				return true;
			}
			elseif ($xmlChunk[0] == "!" || $xmlChunk[0] == "?")
			{
				if (mb_substr($xmlChunk, 0, 4) === "?xml")
				{
					if (preg_match('#encoding\s*=\s*"(.*?)"#i', $xmlChunk, $arMatch))
					{
						$this->fileCharset = $arMatch[1];
						if (mb_strtoupper($this->fileCharset) === mb_strtoupper(LANG_CHARSET))
						{
							$this->fileCharset = false;
						}
						$cs = $this->fileCharset;
					}
				}
			}
			else
			{
				$this->startElement($xmlChunk, $origChunk);
				//check for self-closing tag
				$p = mb_strpos($xmlChunk, ">");
				if (($p !== false) && (mb_substr($xmlChunk, $p - 1, 1) == "/"))
				{
					$this->endElement();
					return true;
				}
			}
		}
		$this->eof = true;

		return false;
	}
	/**
	 * Used to read an XML by chunks started with "<" and endex with "<"
	 *
	 * @return bool
	 *
	 */
	private function getXmlChunk()
	{
		if ($this->bufPosition >= $this->bufLen)
		{
			if (is_resource($this->fileHandler) && !feof($this->fileHandler))
			{
				$this->buf = fread($this->fileHandler, $this->readSize);
				$this->bufPosition = 0;
				$this->bufLen = strlen($this->buf);
			}
			else
			{
				return false;
			}
		}

		//Skip line delimiters (ltrim)
		$xml_position = strpos($this->buf, "<", $this->bufPosition);
		while ($xml_position === $this->bufPosition)
		{
			$this->bufPosition++;
			$this->filePosition++;
			//Buffer ended with white space so we can refill it
			if ($this->bufPosition >= $this->bufLen)
			{
				if (!feof($this->fileHandler))
				{
					$this->buf = fread($this->fileHandler, $this->readSize);
					$this->bufPosition = 0;
					$this->bufLen = strlen($this->buf);
				}
				else
				{
					return false;
				}
			}
			$xml_position = strpos($this->buf, "<", $this->bufPosition);
		}

		//Let's find next line delimiter
		while ($xml_position===false)
		{
			$next_search = $this->bufLen;
			//Delimiter not in buffer so try to add more data to it
			if (!feof($this->fileHandler))
			{
				$this->buf .= fread($this->fileHandler, $this->readSize);
				$this->bufLen = strlen($this->buf);
			}
			else
			{
				break;
			}

			//Let's find xml tag start
			$xml_position = strpos($this->buf, "<", $next_search);
		}
		if ($xml_position===false)
		{
			$xml_position = $this->bufLen+1;
		}

		$len = $xml_position-$this->bufPosition;
		$this->filePosition += $len;
		$result = substr($this->buf, $this->bufPosition, $len);
		$this->bufPosition = $xml_position;

		return $result;
	}
	/**
	 * Stores an element into xml path stack.
	 *
	 * @param string $xmlChunk
	 * @param string $origChunk
	 * @return void
	 *
	 */
	private function startElement($xmlChunk, $origChunk)
	{
		static $search = array(
				"'&(quot|#34);'i",
				"'&(lt|#60);'i",
				"'&(gt|#62);'i",
				"'&(amp|#38);'i",
			);

		static $replace = array(
				"\"",
				"<",
				">",
				"&",
			);

		$p = mb_strpos($xmlChunk, ">");
		if ($p !== false)
		{
			if (mb_substr($xmlChunk, $p - 1, 1) == "/")
			{
				$elementName = mb_substr($xmlChunk, 0, $p - 1);
			}
			else
			{
				$elementName = mb_substr($xmlChunk, 0, $p);
			}

			if (($ps = mb_strpos($elementName, " "))!==false)
			{
				$elementAttrs = mb_substr($elementName, $ps + 1);
				$elementName = mb_substr($elementName, 0, $ps);
			}
			else
			{
				$elementAttrs = "";
			}

			$this->elementStack[] = $elementName;
			$this->positionStack[] = $this->filePosition - strlen($origChunk) - 1;

			if (isset($this->endNodes[$elementName]))
			{
				$xmlPath = implode("/", $this->elementStack);
				if (isset($this->elementHandlers[$xmlPath]))
				{
					$attributes = array();
					if ($elementAttrs !== "")
					{
						preg_match_all("/(\\S+)\\s*=\\s*\"(.*?)\"/s", $elementAttrs, $attrs_tmp);
						if (strpos($elementAttrs, "&") === false)
						{
							foreach ($attrs_tmp[1] as $i=>$attrs_tmp_1)
							{
								$attributes[$attrs_tmp_1] = $attrs_tmp[2][$i];
							}
						}
						else
						{
							foreach ($attrs_tmp[1] as $i=>$attrs_tmp_1)
							{
								$attributes[$attrs_tmp_1] = preg_replace($search, $replace, $attrs_tmp[2][$i]);
							}
						}
					}

					foreach ($this->elementHandlers[$xmlPath] as $callableHandler)
					{
						call_user_func_array($callableHandler, array(
							$xmlPath,
							$attributes,
						));
					}
				}
			}
		}
	}
	/**
	 * Winds tree stack back. Calls (if neccessary) node handlers.
	 *
	 * @return void
	 *
	 */
	private function endElement()
	{
		$elementName = array_pop($this->elementStack);
		$elementPosition  = array_pop($this->positionStack);

		if (isset($this->endNodes[$elementName]))
		{
			$xmlPath = implode("/", $this->elementStack)."/".$elementName;
			if (isset($this->nodeHandlers[$xmlPath]))
			{
				$xmlObject = $this->readXml($elementPosition, $this->filePosition);
				if (is_object($xmlObject))
				{
					foreach ($this->nodeHandlers[$xmlPath] as $callableHandler)
					{
						call_user_func_array($callableHandler, array(
							$xmlObject,
						));
					}
				}
			}
		}
	}
	/**
	 * Reads xml chunk from the file preserving its position
	 *
	 * @param int $startPosition
	 * @param int $endPosition
	 * @return CDataXML|false
	 */
	private function readXml($startPosition, $endPosition)
	{
		$xmlChunk = $this->readFilePart($startPosition, $endPosition);
		if ($xmlChunk && $this->fileCharset)
		{
			$xmlChunk = Encoding::convertEncoding($xmlChunk, $this->fileCharset, LANG_CHARSET);
		}

		$xmlObject = new CDataXML;
		if ($xmlObject->loadString($xmlChunk))
		{
			return $xmlObject;
		}
		return false;
	}

	/**
	 * Reads part of the file preserving its position
	 *
	 * @param int $startPosition
	 * @param int $endPosition
	 * @return string|false
	 */
	public function readFilePart($startPosition, $endPosition)
	{
		if (is_resource($this->fileHandler))
		{
			$savedPosition = ftell($this->fileHandler);
			fseek($this->fileHandler, $startPosition);
			$xmlChunk = fread($this->fileHandler, $endPosition - $startPosition);
			fseek($this->fileHandler, $savedPosition);
			return $xmlChunk;
		}
		return false;
	}
}