_context = &new FATemplateScope; $this->_renderer = &new FATemplateRenderer($this->_context); } function assign($key, $value) { $this->_context->set($key, $value); } function assignArray($array, $prefix = '') { if (is_array($array)) { foreach ($array as $key => $value) { $this->assign($prefix.$key, $value); } } } function assignRef($key, &$value) { $this->_context->set($key, $value); } function &getRef($key) { return $this->_context->get($key); } function getPath($filename) { return $filename; } function &getContext() { return $this->_context; } function &getRenderer() { return $this->_renderer; } function render($filename) { $filename = $this->getPath($filename); if (!is_readable($filename)) { trigger_error("Unable to read template: ".basename($filename), E_USER_WARNING); } else { $dirname = dirname($filename) . '/'; $this->_context->set('_base_dir', $dirname); //$parser->prepare(); $parser = &new FATemplateParser; $root = &$parser->parse(file_get_contents($filename)); //$parser->flush(); $this->_renderer->render($root); } } } class FATemplateRenderer { var $_context; var $_handlers = array(); var $_object_handlers = array(); var $_tag_handlers = array(); var $_text_handlers = array(); function FATemplateRenderer(&$context) { $this->_context = &$context; $this->registerTagHandler('block', new FABlockTag); $this->registerTagHandler('default', new FADefaultTag); $this->registerTagHandler('if', new FAIfTag); $this->registerTagHandler('import', new FAImportTag); $this->registerTagHandler('list', new FAListTag); $this->registerTagHandler('repeat', new FARepeatTag); $this->registerTagHandler('separator', new FASeparatorTag); $this->registerTagHandler('switch', new FASwitchTag); $this->registerTextHandler('~\{\$([a-z0-9_\.]+)((?:\|[^}|]+)*)\}~i', array(&$this, 'doContextVar')); } function doTag(&$node) { $context = &$this->_context; $handler = &$this->getHandler($node); $buffer = ''; if ($handler != NULL) { $handler->setNode($node); if ($handler !== NULL) { $buffer = $handler->parse($context, $this, $node); } } return $buffer; } function doText($buffer) { foreach ($this->_text_handlers as $regex => $callback) { $buffer = preg_replace_callback($regex, $callback, $buffer); } return $buffer; } function doContextVar($match) { $key = $match[1]; $var = $this->_context->get($key); if ($var !== NULL) { if (is_object($var)) { if (isset($this->_object_handlers[get_class($var)])) { $handler = $this->_object_handlers[get_class($var)]; if (is_callable($handler)) { $var = $handler($var); } } else { foreach ($this->_object_handlers as $base => $handler) { if (is_a($var, $base) && is_callable($handler)) { $var = $handler($var); } } } } } if ($match[2]) { $var = $this->runFilters($var, $match[2]); } return $var; } function doObjectVar($match) { $ret = NULL; $object = &$this->_context->get($match[1]); if ($object !== NULL) { $ret = $object->get($match[2]); } return $ret; } function &getHandler(&$node) { $handler = NULL; if (!isset($this->_tag_handlers[$node->getTag()])) { trigger_error("No handler defined for the tag: fa:" . $node->getTag(), E_USER_NOTICE); } else { $handler = $this->_tag_handlers[$node->getTag()]; if (!is_a($handler, 'FATemplateTag')) trigger_error("Invalid handler for the tag: fa" . $node->getTag(), E_USER_ERROR); } return $handler; } function doNode(&$node) { $buffer = ''; $it = &new FAArrayIterator($node->getChildren()); while ($it->next()) { $node = &$it->current(); if (is_a($node, 'FATextNode')) { $buffer .= $this->doText($node->flatten()); } else { $buffer .= $this->doTag($node); } } return $buffer; } function render(&$root) { echo $this->doNode($root); } function registerObjectHandler($class, $handler) { $this->_object_handlers[$class] = $handler; } function registerTagHandler($tag, &$handler) { $this->_tag_handlers[$tag] = &$handler; } function registerTextHandler($regex, $handler) { $this->_text_handlers[$regex] = $handler; } function runFilters($var, $filters) { $filters = explode('|', $filters); while (count($filters) > 1) { $temp = explode(':', array_pop($filters), 2); $filter = $temp[0]; $args = array($var); if (isset($temp[1])) { $args = array_merge($args, explode(',', $temp[1])); } if (is_callable($filter)) $var = call_user_func_array($filter, $args); } return $var; } } class FARootNode { var $_children = array(); function addChild(&$child) { $this->_children[] = &$child; } function flatten() { $it = &new FAArrayIterator($this->_children); $buffer = ''; while ($it->next()) { $child = &$it->current(); $buffer .= $child->flatten(); } return $buffer; } function &getChildren() { return $this->_children; } } class FATextNode { var $_buffer = ''; function FATextNode($text) { $this->_buffer .= $text; } function flatten() { return $this->_buffer; } } class FATagNode extends FARootNode { var $_tag; var $_attribs; function FATagNode($tag, $attribs) { $this->_tag = $tag; $this->_attribs = $attribs; } function getTag() { return $this->_tag; } function getAttribs() { return $this->_attribs; } } class FATemplateParser { function &parse($buffer) { $parts = preg_split('~<(/?)fa:([a-z_]+)((?: [^>]*)?)>~i', $buffer, -1, PREG_SPLIT_DELIM_CAPTURE); $root = &new FARootNode; $tags = &new FAStack; $tags->pushRef($root); for ($i = 0; $i < count($parts); ) { $parent = &$tags->top(); if ($i % 4 == 0) { $node = &new FATextNode($parts[$i]); $parent->addChild($node); $i++; } else { if ($parts[$i]) { if ($parent->getTag() != $parts[$i + 1]) { //trigger_error("Mismatched tag", E_USER_NOTICE); $node = &new FATextNode(''); $parent->addChild($node); } else { $tags->pop(); } } else { $closed = FALSE; if (substr_utf($parts[$i + 2], -2) == ' /') { $closed = TRUE; $parts[$i + 2] = substr_utf($parts[$i + 2], 0, -2); } $node = &$this->parseTag($parts[$i + 1], $parts[$i + 2]); $parent->addChild($node); if (!$closed) { $tags->pushRef($node); } } $i += 3; } } return $root; } function &parseTag($tag, $attrib_string) { $attribs = array(); if ($attrib_string) { preg_match_all('~\s(?P[a-z]+)="(?P[^"]*)"~i', $attrib_string, $temp); foreach ($temp['attrib'] as $i => $attrib) $attribs[$attrib] = $temp['value'][$i]; } $node = &new FATagNode($tag, $attribs); return $node; } } class FATemplateScope { var $_scope = array(); function &get($key) { $ret = ''; $chain = explode('.', $key); $key = array_shift($chain); for ($i = count($this->_scope) - 1; $i >= 0; $i--) { if (isset($this->_scope[$i][$key])) { // PHP4, what a PITA to make me have to do this if (is_object($this->_scope[$i][$key])) { $ret = &$this->_scope[$i][$key]; } else { $ret = $this->_scope[$i][$key]; } break; } } while (!empty($chain)) { $param = array_shift($chain); if (is_array($ret)) { return $ret[$param]; } elseif (is_object($ret) && method_exists($ret, 'get')) { return $ret->get($param); } else { break; } } return $ret; } function set($key, &$value) { $this->_scope[count($this->_scope)][$key] = &$value; } function push(&$scope) { if (is_array($scope)) { $this->_scope[] = &$scope; return TRUE; } } function pop() { array_pop($this->_scope); } } class FATemplateTag { var $attribs; function getAttribute($attrib, $default = NULL) { return (isset($this->attribs[$attrib])) ? $this->attribs[$attrib] : $default; } function &getAttributeArray() { return $this->attribs; } function getAttributeString() { $blacklist = func_get_args(); $ret = ''; $sep = ''; foreach ($this->attribs as $key => $value) { if (!in_array($key, $blacklist)) { $ret .= $sep."$key=\"$value\""; $sep = ' '; } } return $ret; } function isAttribute($attrib) { return (isset($this->attribs[$attrib])); } function parse(&$context, $body) { assert(FALSE); } function requireAttributes() { $attribs = func_get_args(); foreach ($attribs as $attrib) { if (!isset($this->attribs[$attrib])) { trigger_error("Missing $attrib for the tag {$this->tag}", E_USER_ERROR); } } } function requireOne($arglist) { $attribs = func_get_args(); foreach ($attribs as $attrib) { if (isset($this->attribs[$attrib])) return $attrib; } trigger_error("Missing attribute for the tag {$this->tag}", E_USER_ERROR); } function setAttribute($attrib, $value) { $this->attribs[$attrib] = $value; } function setNode(&$node) { $this->tag = $node->getTag(); $this->attribs = $node->getAttribs(); } } ?>