Location: PHPKode > scripts > RogePHP > RoguePHP-0.9.4/RoguePHP-0.9.4/views/XhtmlParser.php
<?php
/**
 * Parse Xhtml views into compiled PHP code
 * @package views
 * @author Cory Marsh
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

define('XHTML_DEBUG', false);
define('XHTML_EOL', "");
define('XHTML_EOT', "");
function xtdbg($msg) { if (XHTML_DEBUG) echo "$msg\n"; }

function ws($msg) { return preg_replace('/\s+/', ' ', $msg); }


class XhtmlParserException extends Exception
{
	public function __construct($message, $code = 1, $previous = null)
	{
		parent::__construct($message, $code, $previous);
	}
}


class XmlMods
{
    private static $_plugins = array();

	public static function parse($code)
	{
		$p = explode('|', $code);
		$v = '%s';

		if (isset($p[1])) {
			if (($epos = stripos(end($p), '\'')) > 1) {
				$end = array_pop($p);
				$suffix = substr($end, $epos);
				$end = substr($end, 0, $epos);
				array_push($p, $end);
			}
			for ($i=1; $i<count($p); $i++) {
				$v = XmlMods::mod($p[$i], $v);
			}
			$out = sprintf("$v", $p[0].$suffix);
			return $out;
		}
		else
			return sprintf("$v", $code);
	}

    public static function mod($mapType, $v)
    {
        $result = $v;
        if (is_numeric($mapType))
		{
			//$sh = $mapType - 3;
            //return "(strlen($v) > $mapType) ? substr($v, 0, $sh) . '...' : $v";
            return "substr($v, 0, $mapType)";
		}

		$data = $mapType;
		$parts = explode(":", $mapType);

        switch($parts[0])
        {
            case "upper":
                $result = "strtoupper($v)";
                break;
            case "lower":
                $result = "strtolower($v)";
                break;
            case "trim":
                $result = "trim($v)";
                break;
            case "url":
                $result = "urlencode($v)";
                break;
            case "deamp":
                $result = "str_replace(array('&amp;', \"'\", '&#039;', '&apos;'), array('&', '', '', ''), $v)";
                break;
            case "cap":
            case "caps":
                $result = "ucfirst($v)";
                break;
            case "allcap":
            case "allcaps":
                $result = "ucwords($v)";
                break;
            case "post":
                $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8', false);
                $result = "$v . '{$parts[1]}'";
                break;
            case "escape":
                $result = "htmlspecialchars($v, ENT_QUOTES, 'UTF-8', false)";
                break;
            case "pre":
                $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8', false);
                $result = "'{$parts[1]}' . $v";
                break;
            case "strip":
                $result = "strip_tags($v)";
                break;
            case "ago":
                $result = "timeAgo($v)";
                break;
            case "strip":
                $result = "strip_tags($v)";
                break;
            default:
                if (isset(self::$_plugins[$mod]))
                {
                    if (isset(self::$_plugins[$mod][1]))
                    {
                        $func = self::$_plugins[$mod][1];
                        $data = $func($data);
                    }
                    $code = self::$_plugins[$mod][0];
                    $result = $code($v);
                }
                else {
					echo "<h1>NO SUCH MOD: [$data]</h1>\n";
					echo "<pre>\n";
					debug_print_backtrace();
                    die("\n no such XHTML modifier: ($mod) ($mapType)\n");
				}
        }

        return $result;
    }

    /**
     * register a new modifier plugin
     * @param string $name the name of the modifier
     * @param string $code a function that returns the code to place into the rendered view.
     *   example: good values, strtolower($v), strreplace("\n", "<br />", $v);
     */
    public static function addPlugin($name, $code)
    {
        self::$_plugins[$name] = $code;
    }
}

/**
 * TODO: DO NOT INCLUDE THE TRAILING ROUGE PANEL CLOSE 
 */
class XhtmlParser
{
    protected $_xml;
    protected $_resources;
    protected $_finalMarkup;
    protected $_iteratorMarkup;

    protected $_mode;
    protected $_source;

    protected $_maxListMap;
    protected $_listStack;
    protected $_templateStack;
    protected $_varStack;
    protected $_conditionList;
    // current pointer needs to be a stack...
    protected $_currentList;
    protected $_nxtPtr;
    protected $_incPtr;
    protected $_currentCloseStack;
	protected $_conditionals;
	protected $_details;

    const NONE = 0;
    const ALL = 1;

    public function __construct()
    {
        $this->_log = Logger::getLogger('xhtml');
        $this->_maxListMap = array();
        $this->_listStack = array();
        $this->_details = array();
        $this->_templateStack = array();
        $this->_currentCloseStack = array();
        $this->_conditionList = array();
        $this->_currentList = '';
		$this->_conditionals = array();
        $this->_nxtPtr = 1;
		$this->addRoot('root');
    }

    /**
     * lookup a language resource and repalce the text.  This method addes the code
     * to the finalMarkup that does the lookup.
     * @param string $resourceName name of the resource to get the text from at runtime
     * @param string $baseName the name of the property to do any replacements in
     */
    protected function handleLanguageResource($baseName, $resourceName)
    {
        // this code is broken!!!
        die("DOES NOT SUPPORT LANGUAGE LOOKUP");

        $p = explode('.', $resourceName);
        $msg = Text::get($p[1], $p[0]);
        if (stristr($msg, '${') === false)
        {
            // this will need to determine the substitution resource {$baseName}
            // should be able to figure out any context
            die("DOES NOT SUPPORT LANGUAGE LOOKUP");
        }

        $this->_varStack[$idx][] = $this->handleLanguageResource(null, $resource);

        // TODO: we might get a perf boost by enclosing the preg_ with an if(stripos) { }
        $code = "\$msg = Text::get('{$p[2]}', '{$p[1]}');\n";
        // test for variable replacements ${foo}
        $code .= "preg_match_all('/(.*?)\\$w!\\{(\\w+)\\}/', \$msg, \$matches);\n";
        $code .= "if (isset(\$matches[0][0])) {";
        $code .= "\$msg = '';";

        $code .= "for(\$i=0,\$m=count(\$matches[0]); \$i<\$m; \$i++)\n {";

        $code .= "\$msg .= \$matches[1][\$i];\n";
        $code .= "if (isset({$baseName}[\$matches[2][\$i]]))\n { \$msg .= {$baseName}[\$matches[2][\$i]]; \n}";
        $code .= "else { \$msg .= \$matches[2][\$i];\n }";
        $code .= "} }";
        $code .= "\$data[] = \$msg;\n";
        return $code;
    }

    /**
     * parse out an xhtml view into compiled php code
     * @param string $xhtmlFile full path or relative to VIEW_DIR constant
     */
    public function parse($xhtmlFile, $outFile, $rootTag = "BODY", $panel = null)
    {
        // initialize a new compiled vieww
        if ($xhtmlFile[0] == '/')
            $this->_source = $xhtmlFile;
        else
            $this->_source = VIEW_DIR . '/' . $xhtmlFile;

        // parse the XML view into php array
        $parser = xml_parser_create();
        if(!xml_parse_into_struct($parser, file_get_contents($this->_source), $this->_xml))
        {
            echo "<pre>\n";
            debug_print_backtrace();
            echo "\n" . xml_error_string(xml_get_error_code($parser)) . "\n";
            die("\nunable to parse: " . $this->_source);
        }
        xml_parser_free($parser);

        // reset the parser state
        $this->_resources = array();
        $this->_iteratorMarkup = array();
        $this->_finalMarkup = "<?php\n";
        $this->_mode = XhtmlParser::ALL;

        $closeTag = '';
        $closeLevel = 0;

        $section = 0;
        $this->Sproc($this->_xml, $panel);
        // make sure the directory exists in the compiled directory first
        $p = explode('/', $xhtmlFile);
        array_pop($p);
        $path = join('/', $p);
        $tmp = umask(0007);
        if (!file_exists(VIEW_CACHE_DIR . '/' . $path))
            mkdir(VIEW_CACHE_DIR . '/' . $path, 0770, true);

        // write out the compiled view
        file_put_contents($outFile, $this->_finalMarkup);
        umask($tmp);
        return;
    }

    
    /**
     * @return string the final markup that we wrote to disk
     */
    public function getMarkup()
    {
        return $this->_finalMarkup;
    }


    /**
     * @param array $tag the tag to test
     * @return boolean true | false 
     */
    private function isOpenTag(array $tag)
    {
		return ($tag['type'] == 'complete' || $tag['type'] == 'open') ? true : false;
        if ($tag['type'] == 'complete' || $tag['type'] == 'open')
            return true;
        return false;
    }

	private function isCdata(array $tag)
    {
		return ($tag['type'] == 'cdata') ? true : false;
    }

	/**
	 * @param array $tag the tag to test
	 * @return boolean true | false 
	 */
    private function isCompleteTag(array $tag)
    {
		return ($tag['type'] == 'complete') ? true : false;
        if ($tag['type'] == 'complete')
            return true;
        return false;
    }

    /**
     * @param array $tag the tag to test
     * @return boolean true | false 
     */
    private function isCloseTag(array $tag)
    {
		return ($tag['type'] == 'close') ? true : false;
        if ($tag['type'] == 'close')
            return true;
        return false;
    }

	/**
	 * @param array $tag the element to test
	 * @return boolean true | false 
	 */
	private function isConditional(array $tag)
	{
		if (isset($tag['attributes'])) {
			foreach ($tag['attributes'] as $name => $value)
				if ($name == 'ROGUE:IF') {
					$this->_conditionals[$this->getLevel($tag)] = 1;
					return true;
				}
		}
		return false;
	}

	/**
	 * @param array $tag the element to test
	 * @return boolean true | false 
	 */
	private function isList(array $tag)
	{
		if (isset($tag['attributes'])) {
			foreach ($tag['attributes'] as $name => $value)
				if ($name == 'ROGUE:LIST')
					return true;
		}
		return false;
	}


    /**
	 * @TODO: debug script and link tags short closing  
     * @param array $tag the tag to test
     * @return boolean true | false 
     */
    private function requireFullClose(array $tag)
    {
        if (isset($tag['content']) || isset($tag['fullclose']) || $tag['tag'] == 'IFRAME' || $tag['tag'] == 'DIV' || $tag['tag'] == 'SCRIPT' || $tag['tag'] == 'TEXTAREA' || (isset($tag['value']) && strlen(ws($tag['value']))))
            return true;
        return false;
    }

	/**
	 * @param array $tag the tag to test
	 * @return boolean true | false 
	 */
    private function isIgnoredTag(array $tag)
    {
        return ($tag['tag'] == 'ROGUE:CONTAINER' || $tag['tag'] == 'ROGUE:CON'
         || $tag['tag'] == 'RO:CONTAINER' || $tag['tag'] == 'RO:CON') ? true : false;
    }

	/**
	 * @param array $elm the element to get the level for
	 * @return string in the format TAGNAME:TAGLEVEL 
	 */
    private function getLevel($elm)
    {
        return $elm['tag'] . ':' . $elm['level'];
    }


    /**
     * @param array $elm the element (ONLY OPEN tags!)
     * @param string $attributeName the name of the attribute to return
     * @return string the value of the attribute or false if not open tag or no attribute found
     */
    private function getAttribute($elm, $attributeName)
    {
        if (!$this->isOpenTag($elm))
            return false;
        if (isset($elm['attributes'][$attributeName]))
            return $elm['attributes'][$attributeName];
        return false;
    }

    /** DATA **/
    protected $_SnxtPtr = 0;
    protected $_StemplateIndex = 0;
    protected $_Stemplate = array();
    protected $_StemplateEnd = array();
    protected $_SidxStack = array();
    protected $_SvarStack = array();
	/** @var array $_Smap map object.value to element name */
    protected $_Smap = array();
    protected $_Sout = array();

    protected $_ScurrentAttributes = array();
    protected $_ScurrentTag = array();
    protected $_Sroots = array();
    protected $_ScurrentContent = '';

	/**
	 * for testing
	 * @return array internal state of the parser 
	 */
    public function getState()
    {
        return array(
            'idxStack' => $this->_SidxStack,
            'templateIndex' => $this->_StemplateIndex,
            'template' => $this->_Stemplate,
            'templateEnd' => $this->_StemplateEnd,
            'idxStack' => $this->_SidxStack,
            'varStack' => $this->_SvarStack,
            'roots' => $this->_Sroots,
            'currentTag' => $this->getTag()
        );
    }

    /**
     * add a new TEMPLATE (lists, and conditionals)
     * @param array $elm XML element
     */
    protected function addTemplate(array $elm)
    {
        xtdbg("ADD TEMPLATE LEVEL : " . $this->getLevel($elm));
        $this->_StemplateIndex = $this->_SnxtPtr++;
        $this->_SidxStack[] = $this->_StemplateIndex;
        $this->_StemplateEnd[$this->_StemplateIndex] = $this->getLevel($elm);
    }

    /**
     * holds reference output to use for the current stack variable.   - NOT - overwrite
     * @param string $key the variable key (with or without LIST: prefix)
     * @param string $output the name of the output (usually parent variable)
     */
    protected function addOutput($key, $output)
    {
        $parts = explode(':', $output);
        $output = end($parts);
        $parts = explode(':', $key);
        $key = end($parts);
        if (!isset($this->_Sout[$key]))
            $this->_Sout[$key] = $output;
    }

    /**
     * holds reference output to use for the current stack variable.    does - OVERWRITE -
     * @param string $key the variable key (with or without LIST: prefix)
     * @param string $output the name of the output (usually parent variable)
     */
    protected function forceOutput($key, $output)
    {
        $parts = explode(':', $output);
        $output = end($parts);
        $this->_Sout[$key] = $output;
    }

    /**
     * - REMOVES THE VARIABLE FROM THE MAP -
     * @param string $key the variable key (with or without LIST: prefix)
     * @return string the name of the output variable
     */
    protected function getOutput($key)
    {
        $parts = explode(':', $key);
        $key = end($parts);
        $result = $this->_Sout[$key];
        unset($this->_Sout[$key]);
        return $result;
    }

    /**
     * append markup to the current template
     * @param array $elm the XML element
     * @param string $markup the markup to add to the template
     */
    protected function appendTemplate(array $elm, $markup)
    {
		// conditionals require a new template
		if ($this->isConditional($elm)) {
			$this->addTemplate($elm);
		}

		// if the tag is a closing tag and the opening tag was a conditional
		if (isset($this->_conditionals[$this->getLevel($elm)])) {
			if (!isset($this->_Stemplate[$this->_StemplateIndex]))
            	$this->_Stemplate[$this->_StemplateIndex] = '';
            $this->_Stemplate[$this->_StemplateIndex] .= $markup;
		}

		// is this a closing tag for a template?  then set the current template to the previous
        $endLvl = (count($this->_StemplateEnd) > 0) ? end($this->_StemplateEnd) : 0;
        $thisLvl = $this->getLevel($elm);
        if ($thisLvl == $endLvl && $this->isCloseTag($elm)) {
            xtdbg($elm['tag'] . " backup template to level: $endLvl");
            array_pop($this->_StemplateEnd);
            $idx = array_pop($this->_SidxStack);
            $this->_StemplateIndex = end($this->_SidxStack);
        }

		// if this is not a contidional tag or ignored then append the markup to the current template
		// dont ignore lists or conditionals....
		if (!isset($this->_conditionals[$this->getLevel($elm)])) {
			if (!isset($this->_Stemplate[$this->_StemplateIndex]))
            	$this->_Stemplate[$this->_StemplateIndex] = '';
            $this->_Stemplate[$this->_StemplateIndex] .= $markup;
		}

		// add new templates for lists
		if (!$this->isConditional($elm) && $this->isList($elm)) {
			$this->addTemplate($elm);
		}

		// this is a hack.   on short close we wont ever get back here to backup to the previous level
		if ($this->isCompleteTag($elm) && ($this->isConditional($elm))) {
			xtdbg($elm['tag'] . " conditional short close case handled");
            array_pop($this->_StemplateEnd);
            $idx = array_pop($this->_SidxStack);
            $this->_StemplateIndex = end($this->_SidxStack);
		}

	}

	/**
	 * add an attribute to the current template output (usually a %s)
	 * @param string $name attribute name
	 * @param string $value the content (%s)
	 * @param string $variable the variable content to replace
	 */
    protected function addAttribute($name, $value, $variable = '')
    {
        $this->_ScurrentAttributes[$name] = $value;//XmlMods::parse($value);
        if ($variable) {
			$root = $this->_Sroots[$this->_StemplateIndex];
            $this->_SvarStack[$root][] = $variable;
        }
    }

	/**
	 * erased the current content and start new content output
	 * @param string $content the content to add
	 * @param string $variable the new root variable
	 */
    protected function replaceContent($content, $variable)
    {
		// if we replace full content, then require full close
		$this->_ScurrentTag['fullclose'] = true;
        $this->_ScurrentContent = '';
        return $this->addContent($content, $variable);
    }

	/**
	 * @param string $content the content to add
	 * @param string $variable the new root variable
	 */
    protected function addContent($content, $variable = '')
    {
        if ($this->_ScurrentTag['tag'] == 'SCRIPT')
            $this->_ScurrentContent .= ltrim($content);
        else
            $this->_ScurrentContent .= preg_replace('/\s+/', ' ', $content);
        if ($variable)
		{
			if (strstr($variable, 'LIST:') !== false)
				$this->addRoot ($variable);
			if (strstr($variable, 'IF:') !== false)
				$this->addRoot ($variable);
			$root = end($this->_Sroots);
			$root = $this->_Sroots[$this->_StemplateIndex];

			if (!isset($this->_SvarStack[$root])) {
				$this->_SvarStack[$root] = array();
                xtdbg("create new root var: $root");
            }
            xtdbg("add variable [$variable] to root [$root]");
            array_push($this->_SvarStack[$root], $variable);
		}
    }

	/**
	 * set the currently working tag, clear attributes, set the tag and clear the current content
	 * @param array $elm the tag
	 */
    protected function setTag(array $elm)
    {
        $this->_ScurrentTag= $elm;
        $this->_ScurrentAttributes = array();
        if (isset($elm['value']) && $this->_ScurrentTag['tag'] != 'SCRIPT') {
            $this->_ScurrentContent = preg_replace('/\s+/', " ", $elm['value']);
		} else if (isset($elm['value'])) {
            $this->_ScurrentContent = preg_replace('/[ \t]+/', ' ', $elm['value']);
		}
    }

	/**
	 * create the markup for the current code and the current content
	 * @return string the template markup content for the current tag
	 */
    protected function getTag()
    {
        $markup = '';
        // open
        if ($this->isOpenTag($this->_ScurrentTag) && !$this->isIgnoredTag($this->_ScurrentTag))
        {
            $markup =  '<' . strtolower($this->_ScurrentTag['tag']);
            foreach ($this->_ScurrentAttributes as $attr => $val)
                $markup .= " $attr=\"".addslashes($val)."\"";
            if (!$this->isCompleteTag($this->_ScurrentTag) || $this->requireFullClose($this->_ScurrentTag))
                $markup .= '>';
        }
        // content
        $markup .= $this->_ScurrentContent;

        // complete
        if (!$this->isIgnoredTag($this->_ScurrentTag)) {
            if ($this->isCompleteTag($this->_ScurrentTag) && !$this->requireFullClose($this->_ScurrentTag))
            {
                xtdbg("SHORT CLOSE");
                $markup .= " />" . XHTML_EOT;
            }
            // complete requires full close...
            else if ($this->isCompleteTag($this->_ScurrentTag))
            {
                xtdbg("IS COMPLETE CLOSE");
                $markup .= "</" . strtolower($this->_ScurrentTag['tag']) . ">" . XHTML_EOT;
            }
            else if ($this->isCloseTag($this->_ScurrentTag))
            {
                xtdbg("IS EXTRA CLOSE [" . $this->_ScurrentTag['type']);
                $markup = '</' . strtolower($this->_ScurrentTag['tag']) . '>';
            }
        }
        xtdbg("GETTAG: " . $this->_ScurrentTag['tag'] .  " -- " . $this->_ScurrentTag['type'] . "= $markup");
        return $markup;
    }

	/**
	 * @param string $attrName the name of the rogue attribute (msg, list, etc)
	 * @param type $value the attribute value
	 * @param array $elm the tab
	 * @return type 
	 */
    protected function SprocessRogueAttr($attrName, $value, array $elm)
    {
        $attrName = substr($attrName, strpos($attrName, ':')+1);
        xtdbg("process rogue attr: ($attrName) : $value : ($elm)");

        // handle resource lookup
        if ($attrName == 'msg') {
            $p = explode('.', $value);
            $msg = Text::get($p[1], $p[0]);
            if (stristr($msg, '${') !== false)
                $msg = $this->handleLanguageResource($msg);
            $this->replaceContent($msg, '');
        }

        // handle lists
        else if ($attrName == 'list') {
			$this->_currentList = $value;
            $this->replaceContent('%s', "LIST:$value");
            return true;
        }
        // handle lists limits
        else if ($attrName == 'max') {
			$this->_details["LISTMAX:{$this->_currentList}"] = $value;
            return true;
		}
        // handle conditionals
        else if ($attrName == 'if') {
			$this->_Stemplate[$this->_StemplateIndex] .= '%s';
            $this->replaceContent('', "IF:$value");
            return true;
        }
        // handle variable content substitutions
        else if ($attrName == 'id') {
            $this->replaceContent('%s', $value);
        }
        // handle variable id attributes substitutions
        else if ($attrName == 'data-id') {
            $this->addAttribute('id', '%s', $value);
        }
        // handle normal attribute substitutions
        else if ($attrName != 'panel') {
            $this->addAttribute($attrName, '%s', $value);
        }
    }


    /**
     * handle a single xml node
     * @param array $elm the element
     */
    protected function ShandleElm(array $elm)
    {
		// flush out stale content
		$this->_ScurrentContent = '';
        xtdbg("handle tag: " . $elm['tag']);
        $this->setTag($elm);

        // if we are only rendereing a panel and we hit that panel, then start rendering
        if ($this->isOpenTag($elm))// && $this->getAttribute($elm, 'ROGUE:PANEL') == $panel)
        {
            if (isset($elm['attributes']))
            {
                xtdbg($elm['tag'] . " has attrs");
                // handle rogue attributes (not rogue-id)
                foreach ($elm['attributes'] as $attribute => $value)
                {
                    $attribute = strtolower($attribute);
                    if ($attribute == 'rogue:id' || $attribute == 'ro:id')
                        continue;
                    if (stristr($attribute, 'rogue:') !== false || stristr($attribute, 'ro:') !== false)
                        $this->SprocessRogueAttr($attribute, $value, $elm);
                    else
                        $this->addAttribute($attribute, $value);
                }
                // handle rogue-id attribute
                foreach ($elm['attributes'] as $attribute => $value)
                {
                    $attribute = strtolower($attribute);
                    if ($attribute == 'rogue:id' || $attribute == 'ro:id')
                        $this->SprocessRogueAttr($attribute, $value, $elm);
                }
            }
        }

        $this->appendTemplate($elm, $this->getTag());
    }

    /**
     * process a lsit of XML nodes
     * @param array $xml
     * @param string $panel the rogue panel to render
     * @return string
     */
    protected function Sproc(array $xml, $panel = null)
    {
        $this->addTemplate(array('tag' => 'root', 'level' => 0));

        $closeLevel = '';
        // the markup created from this tag
        foreach ($xml as $elm)
        {
            xtdbg("ELM: " . $elm['tag'] . ' - ' . $elm['type']);
            $level = $this->getLevel($elm);

            // handle the panel open
            if ($this->isOpenTag($elm) && ($this->getAttribute($elm, 'ROGUE:PANEL') == $panel || $panel == null))
            {
                if ($this->isCompleteTag($elm))
                    throw new XhtmlParserException("ROGUE Panels must have at least 1 sub element (consider using ROGUE:CONTAINER).");
                $closeLevel = $level;
            }
            
            // continue rendering this section
            if ($closeLevel != '')
            {
                $this->ShandleElm($elm);
            }

            if ($this->isCloseTag($elm) && $level == $closeLevel)
            {
                xtdbg("close rogue panel: $level / $closeLevel");
                break;
            }
        }

        //print_r($this->getState());
		for ($i=0; $i<count($this->_Stemplate); $i++)
        {
			$name = $this->getFriendlyRoot($i);
            $this->_finalMarkup .= "\$template_{$name}=<<<EOT\n".$this->_Stemplate[$i]."\nEOT;\n";
        }
		$this->_finalMarkup .= "\$data_root = array();\n";
		$this->_finalMarkup .= "\$templateOut_root = '';\n";
		$this->SrecurseBuild('root');
        $this->_finalMarkup .= "\$result = vsprintf(\$template_root, \$data_root);\n";
        //$this->_finalMarkup .= "\$result = print_r(\$data_root, true);\n";
	}


    protected function SrecurseBuild($root, $doOut = true)
	{
        if (strstr($root, 'LIST:') !== false)
            $name = substr($root, 5);
        else if (strstr($root, 'IF:') !== false)
            $name = substr($root, 3);
        else
            $name = $root;

		// what we need to do loop over the vars and add them to the template
		//for ($i=1; $i<count($this->_SvarStack); $i++)
		foreach ($this->_SvarStack[$root] as $index => $var)
		{
			if (strstr($var, 'LIST:') !== false)
			{
				$varName = substr($var, 5);
				//$this->getTemplateMarkup($varName);
				$this->_finalMarkup .= $this->getStartListMarkup($root, $var, $name);
				//$vn = $this->_SvarStack[$varName][0];
                $this->_finalMarkup .= "//recurse START $varName\n";
                $this->_Smap[$varName] = "\$item_{$varName}";
                // MIGHT NEED TO RETURN OUTPUT HERE.
                // ALSO NEED TO _NOT_ ALWAYS ADD FINAL MARKUP BEFORE RECURSE
                $this->SrecurseBuild($varName, true);
                $this->_finalMarkup .= "//recurse FIN $varName\n\n";
                $this->_finalMarkup .= $this->getFinListMarkup($root, $var);
			}
            else if (strstr($var, 'IF:') !== false)
			{
				$varName = substr($var, 3);
				// need to test if this is a COMPLETE and adjust correctly...
				$this->getTemplateMarkup($varName);
				//$out = $this->getIfMarkup($root, $var, $name);
				//$out .= $this->SrecurseBuild($varName, false);
				$this->_finalMarkup .= $this->getStartIfMarkup($root, $var, $name) . " // START IF\n";
				$this->addOutput($var, $this->_SvarStack[$varName][0]);

				$inner = $this->SrecurseBuild($varName, false);
				if ($inner)
					$this->_finalMarkup .= $inner;
				//else if ($this->_SvarStack[$root][$index+1] == $varName)
				//	$this->_finalMarkup .= $this->getVarMarkup($varName, $var);
				$this->_finalMarkup .= $this->getFinIfMarkup($root, $var, $name) . " // END IF $root, $var, $name\n";
			}
			else {
				$this->_finalMarkup .= $this->getVarMarkup($root, $var);
			}
				//$out = $this->getVarMarkup($root, $var);
				
			//$this->_finalMarkup .= $out;
			$this->_finalMarkup .= "####### ROW\n\n";
            unset ($this->_SvarStack[$root]);
		}
        xtdbg("finish rogue proc");
    }

	protected function getTemplateMarkup($name)
	{
		$short = $this->makeName($name);
		$this->_finalMarkup .= "\$data_{$short} = array();\n";
		$this->_finalMarkup .= "\$templateOut_{$short} = '';\n";
	}

    protected function getStartIfMarkup($root, $name, $parentName)
	{
        $out = "// PARENT: $root, $name, $parentName\n";
		$varName = $this->getVarName($root, $name);
        $out .= "if(isset($varName) && $varName) {\n";
        return $out;
    }

    protected function getFinIfMarkup($root, $name, $parentName)
    {
        $short = $this->makeName($name);
		$out = "// FIN PARENT: $root, $name, $parentName, $short\n";
		// handle the case where we have an inner template (like a list)
		if (isset($this->_Sout[$short]) && $this->_Sout[$short]) {
			$out .= "\$data_{$short}[] = \$templateOut_{$varName}; // Sout\n";
			$out .= "\$data_{$root}[] = vsprintf(\$template_{$short}, \$data_{$short});\n";
			$out .= '}';
		}
		// note sure about this case
		else if ($varName) {
			die("CONDITIONAL CASE NOT HIT: $varName\n");
        	$out .= "\$data_{$parentName}[] = \$templateOut_{$short}; }\n";
		}
		// handle the case of an rogue:id and rogue:if on the same element
		else {
			$out .= $this->getVarMarkup($short, $short, true);
			$out .= "\$data_{$root}[] = vsprintf(\$template_{$short}, \$data_{$short});\n";
			$out .= '}';
		}
        $out .= "else { \$data_{$parentName}[] = ''; }\n";
        return $out;
	}

	protected function getStartListMarkup($root, $name, $parentName)
	{
		$short = $this->makeName($name);
		$varName = $this->getVarName($root, $name);

		//$out = "\$data_{$parentName} = array(); // OUT LIST $parentName\n";
		$out = "\$templateOut_{$short} = '';\n";
$out .= "if(isset($varName)) {\n";
$out .= "\$list_{$short} = ($varName instanceOf iList) ? {$varName}->getArray() : $varName;\n";
$out .= "\$sz_{$short} = count(\$list_{$short}); \n";
if (isset($this->_details["LISTMAX:$short"])) { 
	$out .= "\$sz_{$short} = (\$sz_{$short} > {$this->_details["LISTMAX:$short"]}) ? {$this->_details["LISTMAX:$short"]} : \$sz_{$short};\n";
}
	
$out .= "for(\$i_{$short}=0; \$i_{$short}<\$sz_{$short}; \$i_{$short}++) {\n";
//$out .= "\$data_{$short} = array(); // IN LIST $parentName - $short\n";
$out .= "\$item_{$short} = \$list_{$short}[\$i_{$short}];\n";
$out .= "\$data_{$short} = array();\n";
return $out;
	}

	protected function getFinListMarkup($root, $name)
	{
		$short = $this->makeName($name);
$out = "\$templateOut_{$short} .= vsprintf(\$template_{$short}, \$data_{$short});\n";
$out .= "} }\n";
if (isset($this->_Sout[$short]))
	$root = $this->_Sout[$short];
$out .= "\$data_{$root}[] = \$templateOut_{$short};\n";
return $out;
	}

	/**
	 * return the markup for a single variable replacement
	 * @param string $root the root element to get the var from (list, root node, etc)
	 * @param string $var the variable to get
	 * @param boolean $complete override detection of simple list values with complete get
	 * @return string the program code 
	 */
	protected function getVarMarkup($root, $var, $complete = false)
	{
        if ($root != $var || $complete)
        {
				//die ("INDEX! [$root] [$var] [$comlete]"); }
            $varName = $this->getVarName($root, $var);
            $varName = str_replace('.', '_', $varName);
			$varValue = XmlMods::parse($varName);
			$modPos = explode('|', $varName);
			if (isset($modPos[1]))
				$varName = $modPos[0] . substr($varName, -2);

			/*
			if (($epos = strripos($varName, '\']')) > 1) {
				$varName .= substr($varName, $epos, strlen($varName));
			} */

            $root = str_replace('.', '_', $root);
			$bpos = strrpos($varName, ">");
			$varName = ($bpos > 7) ? substr($varName, 0, $bpos-1) : $varName;
			
			if($root != 'root' && $this->startsWith($var, "index")) {
				$parts = explode('|', $var);
				if (isset($parts[1])) {
					$parts[0] = "\$i_$root";
					$var = XmlMods::parse(implode('|', $parts), '%s');
				} else {
					$var = "\$i_$root";
				}
            	return "\$data_{$root}[] = $var;\n";
			}
            return "\$data_{$root}[] = (isset($varName)) ? $varValue : '';\n";
        }
		// handle the case where the list is just a value, not a key value
        else
        {
            return "\$data_{$root}[] = \$item_{$root};\n";
        }
	}

	// what we need to do loop over the vars and add them to the template
	protected function getVarName($i, $var)
	{
		// TODO: add index number here
		xtdbg("GET VAR NAME: [$i] $var");
        if (strpos($var, 'LIST:') !== false)
		{
			xtdbg("TEMP1: $var");
			$var = substr($var, 5);
			//xtdbg("TEMP2: $var");
			$this->addRoot($var, 5);
		}
        if (strpos($var, 'IF:') !== false)
		{
			xtdbg("TEMP1: $var");
			$var = substr($var, 3);
			//xtdbg("TEMP2: $var");
			$this->addRoot($var, 5);
		}

		$parts = explode('.', $var);
		$last = strrpos($var, '.');
		$root = ($last) ? substr($var, 0, $last) : 'root';
        $method = end($parts);
		$base = (isset($parts[1]))? $parts[count($parts)-2] : $var;

		$pos = array_search($root, $this->_Sroots);
		if ($pos === false)
		{
			$root = 'root';
		}

		$res = ($root != 'root') ? $res = "\$data{$pos}['$var']" : "\$this->root['$base']";
		//echo "<pre>\n";
		//print_r($parts);
		//die("$var: $bar, $res");

        if (isset($this->_Smap[$root]))
        {
			$res = $this->_Smap[$root];
		}
                xtdbg("#### VAR NAME: LAST: $last VAR: $var ROOT: $root");

                $pos = array_search($root, $this->_Sroots);
                if ($pos === false)
                {
					xtdbg("%%%%% $i - $var - $last - $root");
					//die("no such root: [$root]");
					/*
					print_r($this->_Smap);
					die("MAP");
					return "\$this->root['$var']";
					 * 
					 */
                }
                //xtdbg("%%%%% __found id__ $i - $var - $last - $root");
                //throw new XhtmlParserException("unable to map xhtml var [$root] - " . print_r($this->_Sroots, true));


				if (isset($parts[1])) {
            		if (strstr($method, "get") === 0 || strstr($method, '(')) {
						if (strstr($method, '(') === false)
							$method .= '()';
						$res .=  "->$method";
					}
					else
                		$res .=  "['{$method}']";
				}


				return $res;






		$last = strrpos($var, '.');
		$root = ($last) ? substr($var, 0, $last) : 'root';
        $method = ($last) ? substr($var, $last) : $var;

		/*
		$parts = explode('.', $var);
		$root = 'root';//($last) ? substr($var, 0, $last) : 'root';
		//foreach ($parts as $part) {
		print_r($this->_Sroots);
		for ($i=0,$m=count($parts);$i<$m;$i++) {
			echo "TEST: {$parts[$i]}\n";
			if (array_search($parts[$i], $this->_Sroots) !== false)
				$root = $parts[$i];
		}
		die ($var);
        $method = end($parts);
		 */

        if (isset($this->_Smap[$root]))
        {
            if (strstr($method, "get") === 0 || strstr($method, '(')) {
                if (strstr($method, '(') === false)
                    $method .= '()';
                die("MAP TEST : ::: " .$this->_Smap[$root] . "->$method\n;");
                return $this->_Smap[$root] . "->$method";
                return XmlMods::parse($this->_Smap[$root] . "->$method");
            } else {
                return $this->_Smap[$root] . "['{$method}']";
            }
            //die("have root: $root : " . $this->_Smap[$root]);
        }
		xtdbg("#### VAR NAME: LAST: $last VAR: $var ROOT: $root");

		$pos = array_search($root, $this->_Sroots);
		if ($pos === false)
		{
			$root = 'root';
			xtdbg("%%%%% $i - $var - $last - $root");
			print_r($this);
            die("no such root: [$root]");
			return "'cant find $root';";
		}
		//xtdbg("%%%%% __found id__ $i - $var - $last - $root");
		//throw new XhtmlParserException("unable to map xhtml var [$root] - " . print_r($this->_Sroots, true));
		if ($root == 'root')
			return "\$this->root['$var']";
		return "\$data_{$pos}['$var']";
	}

	protected function addRoot($name)
	{
		xtdbg("add root [$name]");
        if (strstr($name, 'LIST:'))
        {
            $this->_Sroots[] = substr($name, 5);
        }
        else if (strstr($name, 'IF:'))
        {
            $this->_Sroots[] = substr($name, 3);
        }
        else
        {
            $this->_Sroots[] = $name;
        }
	}

	protected function makeName($listName)
	{
		if (strstr($listName, 'LIST:') !== false)
			$listName = substr ($listName, 5);
		else if (strstr($listName, 'IF:') !== false)
			$listName = substr ($listName, 3);
		$listName = preg_replace('/[^a-zA-Z0-9]/', '_', $listName);
		return $listName;
	}

	protected function getFriendlyRoot($i)
	{
		$name = $this->_Sroots[$i];
		$name = preg_replace('/[^a-zA-Z0-9]/', '_', $name);
		return $name;
	}


	protected function startsWith($haystack, $needle)
	{
		return (substr($haystack, 0, strlen($needle)) === $needle);
	}

	protected function endsWith($haystack, $needle)
	{
		return (substr($haystack, strlen($needle) * -1) === $needle);
	}
}
?>
Return current item: RogePHP