QuickBooks/XML/Backend/Builtin.php
protected function _parseHelper($xml, &$Root, &$errnum, &$errmsg, $indent = 0)
	{
		$errnum = QuickBooks_XML::ERROR_OK;
		$errmsg = '';
		
		$arr = array();
		$xml = trim($xml);

		if (!strlen($xml))
		{
			return false;
		}

		$data = '';

		$vstack = array();
		$dstack = array();

		// Remove comments
		while (false !== strpos($xml, '<!--'))
		{
			$start = strpos($xml, '<!--');
			$end = strpos($xml, '-->', $start);

			if (false !== $start and false !== $end)
			{
				$xml = substr($xml, 0, $start) . substr($xml, $end + 3);
			}
			else
			{
				break;
			}
		}

		$raw = $xml;
		$current = 0;
		$last = '';

		$i = 0;

		// Parse
		while (false !== strpos($xml, '<'))
		{
			/*
			print('now examinging:');
			print('--------------');
			print($xml);
			print('-----------');
			print("\n\n\n");
			*/
			
			$opentag_start = strpos($xml, '<');
			$opentag_end = strpos($xml, '>');
			
			// CDATA check
			if (substr($xml, $opentag_start, 3) == '<![')
			{
				// Find the end of the CDATA section
				$cdata_end = strpos($xml, ']]>');
				
				$opentag_start = strpos($xml, '<', $cdata_end + 3);
				$opentag_end = strpos($xml, '>', $cdata_end + 3);
			}
			
			//print('opentag start/end (' . $opentag_start . ', ' . $opentag_end . ') puts us at: {{' . substr($xml, $opentag_start, $opentag_end - $opentag_start) . '}}' . "\n\n");
			
			$tag_w_attrs = trim(substr($xml, $opentag_start + 1, $opentag_end - $opentag_start - 1));
			
			$tag = '';
			$attributes = array();
			$this->_extractAttributes($tag_w_attrs, $tag, $attributes);
			
			if (substr($tag_w_attrs, 0, 1) == '?')		// xml declration
			{
				// ignore
			}
			else if (substr($tag_w_attrs, 0, 1) == '!')
			{
				// ignore
			}
			//else if (substr($tag_w_attrs, 0, 3) == '!--')		// comment
			//{
			//	// ignore
			//}
			else if (substr($tag_w_attrs, -1, 1) == '/')
			{
				// ***DO NOT*** completely ignore, auto-closed because it has no children
				// Completely ignoring causes some SOAP errors for requests like <serverVersion xmlns="http://developer.intuit.com/" />
				
				//print('TAG: [' . substr($tag_w_attrs, 0, -1 . ']' . "\n");
				//print('TWA: [' . $tag . ']' . "\n");
				
				//$tag_w_attrs = substr($tag_w_attrs, 0, -1);
				//$tag = substr($tag, 0, -1);
				
				$tag_w_attrs = rtrim($tag_w_attrs, '/');
				$tag = rtrim($tag, '/');
				
				// Shove the item on to the stack
				array_unshift($vstack, array( $tag, $tag_w_attrs, $current + $opentag_end ) );
				array_unshift($dstack, array( $tag, $tag_w_attrs, $current + $opentag_end ) );
				
				$key = key($vstack);
				$tmp = array_shift($vstack);
				
				$pop = $tag;
				$gnk = $tag_w_attrs;
				$pos = $current + $opentag_end;
				
				// there is no data, so empty data and the length is 0
				$length = 0;
				$data = null;
				
				if (count($vstack))
				{
					array_shift($dstack);
				}
				else
				{
					$dstack[$key] = array( $pop, $gnk, $pos, $length, $data );
				}
			}
			else if (substr($tag_w_attrs, 0, 1) == '/')		// close tag
			{
				// NOTE: If you change the code here, you'll likely have to 
				//	change it in the above else () section as well, as that 
				//	section handles data-less tags like <serverVersion />
				
				$tag = substr($tag, 1);
				
				$key = key($vstack);
				$tmp = array_shift($vstack);
				
				$pop = $tmp[0];
				$gnk = $tmp[1];
				$pos = $tmp[2];
				
				if ($pop != $tag)
				{
					$errnum = QuickBooks_XML::ERROR_MISMATCH;
					$errmsg = 'Mismatched tags, found: ' . $tag . ', expected: ' . $pop;
					
					return false;
				}
				
				$data = substr($raw, $pos, $current + $opentag_start - $pos);
				
				// Handle <![CDATA[ ... ]]> sections
				if (substr($data, 0, 9) == '<![CDATA[')
				{
					$cdata_end = strpos($data, ']]>');
					
					// Set the data to the CDATA section...
					$data = QuickBooks_XML::encode(substr($data, 9, $cdata_end - 9));
					
					// ... and remove the CDATA from the remaining XML string
					//$current = $current + strlen($data) + 12; 
				}
				
				if (count($vstack))
				{
					array_shift($dstack);
				}
				else
				{
					$dstack[$key] = array( $pop, $gnk, $pos, $current + $opentag_start - $pos, $data );
				}
			}
			else	// open tag
			{
				array_unshift($vstack, array( $tag, $tag_w_attrs, $current + $opentag_end + 1 ) );
				array_unshift($dstack, array( $tag, $tag_w_attrs, $current + $opentag_end + 1 ) );
			}
			
			//print('stacks' . "\n");
			//print_r($vstack);
			//print_r($dstack);
			
			$xml = substr($xml, $opentag_end + 1);
			
			$current = $current + $opentag_end + 1;
		}

		if (strlen($xml))
		{
			$errnum = QuickBooks_XML::ERROR_GARBAGE;
			$errmsg = 'Found this garbage data at end of stream: ' . $xml;
			return false;
		}

		if (count($vstack))
		{
			$errnum = QuickBooks_XML::ERROR_DANGLING;
			$errmsg = 'XML stack still contains this after parsing: ' . var_export($vstack, true);
			return false;
		}

		//print_r($dstack);
		//exit;
		
		$dstack = array_reverse($dstack);

		$last = '';
		foreach ($dstack as $node)
		{
			$tag = $node[0];
			$tag_w_attrs = $node[1];
			$start = $node[2];

			if (count($node) < 5)
			{
				continue;
			}

			$length = $node[3];
			$payload = $node[4];

			$tmp = '';
			$attributes = array();
			$this->_extractAttributes($tag_w_attrs, $tmp, $attributes);
			
			$Node = new QuickBooks_XML_Node($tag);
			foreach ($attributes as $key => $value)
			{
				$value = QuickBooks_XML::decode($value, true);
				
				$Node->addAttribute($key, $value);
			}
			
			if (false !== strpos($payload, '<'))
			{
				// The tag contains child tags 
				
				$tmp = $this->_parseHelper($payload, $Node, $errnum, $errmsg, $indent + 1);
				if (!$tmp)
				{
					return false;
				}
			}
			else
			{
				// This tag has no child tags contained inside it
				
				// Make sure we decode any entities
				$payload = QuickBooks_XML::decode($payload, true);
				
				$Node->setData($payload);
			}

			$Root->addChild($Node);

			$last = $tag;
		}

		return $Root;
	}
protected function _parseHelper($xml, &$Root, &$errnum, &$errmsg, $indent = 0)
	{
		$errnum = QuickBooks_XML::ERROR_OK;
		$errmsg = '';
		
		$arr = array();
		$xml = trim($xml);

		if (!strlen($xml))
		{
			return false;
		}

		$data = '';

		$vstack = array();
		$dstack = array();

		// Remove comments
		while (false !== strpos($xml, '<!--'))
		{
			$start = strpos($xml, '<!--');
			$end = strpos($xml, '-->', $start);

			if (false !== $start and false !== $end)
			{
				$xml = substr($xml, 0, $start) . substr($xml, $end + 3);
			}
			else
			{
				break;
			}
		}

		$raw = $xml;
		$current = 0;
		$last = '';

		$i = 0;

		// Parse
		while (false !== strpos($xml, '<'))
		{
			/*
			print('now examinging:');
			print('--------------');
			print($xml);
			print('-----------');
			print("\n\n\n");
			*/
			
			$opentag_start = strpos($xml, '<');
			$opentag_end = strpos($xml, '>');
			
			// CDATA check
			if (substr($xml, $opentag_start, 3) == '<![')
			{
				// Find the end of the CDATA section
				$cdata_end = strpos($xml, ']]>');
				
				$opentag_start = strpos($xml, '<', $cdata_end + 3);
				$opentag_end = strpos($xml, '>', $cdata_end + 3);
			}
			
			//print('opentag start/end (' . $opentag_start . ', ' . $opentag_end . ') puts us at: {{' . substr($xml, $opentag_start, $opentag_end - $opentag_start) . '}}' . "\n\n");
			
			$tag_w_attrs = trim(substr($xml, $opentag_start + 1, $opentag_end - $opentag_start - 1));
			
			$tag = '';
			$attributes = array();
			$this->_extractAttributes($tag_w_attrs, $tag, $attributes);
			
			if (substr($tag_w_attrs, 0, 1) == '?')		// xml declration
			{
				// ignore
			}
			else if (substr($tag_w_attrs, 0, 1) == '!')
			{
				// ignore
			}
			//else if (substr($tag_w_attrs, 0, 3) == '!--')		// comment
			//{
			//	// ignore
			//}
			else if (substr($tag_w_attrs, -1, 1) == '/')
			{
				// ***DO NOT*** completely ignore, auto-closed because it has no children
				// Completely ignoring causes some SOAP errors for requests like <serverVersion xmlns="http://developer.intuit.com/" />
				
				//print('TAG: [' . substr($tag_w_attrs, 0, -1 . ']' . "\n");
				//print('TWA: [' . $tag . ']' . "\n");
				
				//$tag_w_attrs = substr($tag_w_attrs, 0, -1);
				//$tag = substr($tag, 0, -1);
				
				$tag_w_attrs = rtrim($tag_w_attrs, '/');
				$tag = rtrim($tag, '/');
				
				// Shove the item on to the stack
				array_unshift($vstack, array( $tag, $tag_w_attrs, $current + $opentag_end ) );
				array_unshift($dstack, array( $tag, $tag_w_attrs, $current + $opentag_end ) );
				
				$key = key($vstack);
				$tmp = array_shift($vstack);
				
				$pop = $tag;
				$gnk = $tag_w_attrs;
				$pos = $current + $opentag_end;
				
				// there is no data, so empty data and the length is 0
				$length = 0;
				$data = null;
				
				if (count($vstack))
				{
					array_shift($dstack);
				}
				else
				{
					$dstack[$key] = array( $pop, $gnk, $pos, $length, $data );
				}
			}
			else if (substr($tag_w_attrs, 0, 1) == '/')		// close tag
			{
				// NOTE: If you change the code here, you'll likely have to 
				//	change it in the above else () section as well, as that 
				//	section handles data-less tags like <serverVersion />
				
				$tag = substr($tag, 1);
				
				$key = key($vstack);
				$tmp = array_shift($vstack);
				
				$pop = $tmp[0];
				$gnk = $tmp[1];
				$pos = $tmp[2];
				
				if ($pop != $tag)
				{
					$errnum = QuickBooks_XML::ERROR_MISMATCH;
					$errmsg = 'Mismatched tags, found: ' . $tag . ', expected: ' . $pop;
					
					return false;
				}
				
				$data = substr($raw, $pos, $current + $opentag_start - $pos);
				
				// Handle <![CDATA[ ... ]]> sections
				if (substr($data, 0, 9) == '<![CDATA[')
				{
					$cdata_end = strpos($data, ']]>');
					
					// Set the data to the CDATA section...
					$data = QuickBooks_XML::encode(substr($data, 9, $cdata_end - 9));
					
					// ... and remove the CDATA from the remaining XML string
					//$current = $current + strlen($data) + 12; 
				}
				
				if (count($vstack))
				{
					array_shift($dstack);
				}
				else
				{
					$dstack[$key] = array( $pop, $gnk, $pos, $current + $opentag_start - $pos, $data );
				}
			}
			else	// open tag
			{
				array_unshift($vstack, array( $tag, $tag_w_attrs, $current + $opentag_end + 1 ) );
				array_unshift($dstack, array( $tag, $tag_w_attrs, $current + $opentag_end + 1 ) );
			}
			
			//print('stacks' . "\n");
			//print_r($vstack);
			//print_r($dstack);
			
			$xml = substr($xml, $opentag_end + 1);
			
			$current = $current + $opentag_end + 1;
		}

		if (strlen($xml))
		{
			$errnum = QuickBooks_XML::ERROR_GARBAGE;
			$errmsg = 'Found this garbage data at end of stream: ' . $xml;
			return false;
		}

		if (count($vstack))
		{
			$errnum = QuickBooks_XML::ERROR_DANGLING;
			$errmsg = 'XML stack still contains this after parsing: ' . var_export($vstack, true);
			return false;
		}

		//print_r($dstack);
		//exit;
		
		$dstack = array_reverse($dstack);

		$last = '';
		foreach ($dstack as $node)
		{
			$tag = $node[0];
			$tag_w_attrs = $node[1];
			$start = $node[2];

			if (count($node) < 5)
			{
				continue;
			}

			$length = $node[3];
			$payload = $node[4];

			$tmp = '';
			$attributes = array();
			$this->_extractAttributes($tag_w_attrs, $tmp, $attributes);
			
			$Node = new QuickBooks_XML_Node($tag);
			foreach ($attributes as $key => $value)
			{
				$value = QuickBooks_XML::decode($value, true);
				
				$Node->addAttribute($key, $value);
			}
			
			if (false !== strpos($payload, '<'))
			{
				// The tag contains child tags 
				
				$tmp = $this->_parseHelper($payload, $Node, $errnum, $errmsg, $indent + 1);
				if (!$tmp)
				{
					return false;
				}
			}
			else
			{
				// This tag has no child tags contained inside it
				
				// Make sure we decode any entities
				$payload = QuickBooks_XML::decode($payload, true);
				
				$Node->setData($payload);
			}

			$Root->addChild($Node);

			$last = $tag;
		}

		return $Root;
	}
XML parsing recursive helper function

@param string $xml
@param QuickBooks_XML_Node $root
@return void