<?php

/*
    This program demonstrates mayhem caused by iterating over a DOMNode's children
    using firstChild and nextSibling TAKEN BY REFERENCE.
    
    It instantiates a DOMDocument, loads an XML fragment into it and processes it
    three times - the good way, the mixed way (read below) and the evil way. Each time
    recursion progress is logged and (trivial) transformation result is dumped
    (if not errored out earlier).
    
    * First time it takes a path in renderObject() that iterates over
      child nodes using a DOMNodeList obtained with a DOMXPath query.
      
    * Second time it uses DOMNodeList at the outermost level in renderObject()
      but  firstChild/nextSibling by reference at inner levels. This
      demonstrates very nice trashing of local variables from which
      transformation result is assembled in renderObject().
      
    * Third time it always uses firstChild/nextSibling in renderObject().
      On my machine it errors out at the outermost level but AFTER returning
      from recursion.
*/


define('GOOD_WAY', true);
define('EVIL_WAY', false);

//The XML is formatted so that objects only contain params
//and no text or whitespace in between
$xmlfragment = <<<XMLFRAGMENT
<root>
    Some text.
    <object type="foo" name="bar"><param name="__default_content">
	Some parameter text.
	Some parameter text.
	<object type="bar" name="nested"><param name="__default_content">
	    Some nested parameter text.
	    Some nested parameter text.
	    <object type="baz" name="deeply_nested"><param name="__default_content">
	    </param><param name="bold">true</param></object>
	    More nested parameter text
	    More nested parameter text
	</param></object>
    </param></object>
    Some more text    
</root>
XMLFRAGMENT;

$doc = new DOMDocument("1.0", "utf-8"); $doc->loadXML($xmlfragment);

$root = $doc->childNodes->item(0);

//uncomment to display errors
//ini_set("display_errors", "On"); 

echo "THE GOOD WAY (DOMNodeList):\n\n";
echo "Recursion structure:\n\n";
$innerWay = GOOD_WAY;
$result=render($root, true);
echo "\nTransformation result:\n$result\n";
echo "Success!\n\n";

echo "THE MIXED WAY (DOMNodeList at outermost level, then firstChild/nextSibling by ref):\n\n";
echo "Recursion structure:\n\n";
$innerWay = EVIL_WAY;
$result=render($root, true);
echo "\nTransformation result:\n$result\n";
echo "Is it really a success?\n\n";

echo "THE EVIL WAY (firstChild/nextSibling by ref):\n\n";
echo "Recursion structure:\n\n";
$result=render($root, false);
echo "\nTransformation result:\n$result\n";
echo "Unexpected success!\n\n";

function render(&$node, $whichWay) {

    $ret="";
    //for each node
    for($i=0; $i<$node->childNodes->length; ++$i) {
    
	$child = $node->childNodes->item($i);

	//if text node, append
	if ($child instanceof DOMText)  $ret .= $child->wholeText;

	//if <object>, render and append
	else if ($child instanceof DOMElement && $child->tagName == "object")
	
	    $ret .= renderObject($child, $whichWay);
    }
    return $ret;
}

function renderObject(&$node, $whichWay) {

    $objectName = $node->getAttribute("name");
    $objectType = $node->getAttribute("type");
    $params = array();
    
    if ($whichWay==GOOD_WAY) {
    
	$xpath = new DOMXPath($node->ownerDocument);
	$paramNodes = $xpath->evaluate("./param", $node);
	for($i=0; $i<$paramNodes->length; ++$i) {
	
	    $paramNode = $paramNodes->item($i);
            $paramName = $paramNode->getAttribute("name");
	    echo "->render()\n";
            $paramValue = render($paramNode, $GLOBALS['innerWay']); //which way to use at inner levels?
            echo "<-\n";
	    $params[$paramName] = $paramValue;
	}
    }
    
    else { //($which_way == EVIL_WAY)
    
	for($paramNode = &$node->firstChild; $paramNode; $paramNode = &$paramNode->nextSibling) {
	
            $paramName = $paramNode->getAttribute("name");
	    echo "->render()\n";
            $paramValue = render($paramNode, false);
            echo "<-\n";
            $params[$paramName]=$paramValue;
	}
    }
    
    //build some simple transformation result
    $ret = "[object type='$objectType' name='$objectName']";
    foreach($params as $n => $v) $ret.="[param name='$n']{$v}[/param]";
    $ret.="[/object]";
    return $ret;
}

