[limb-svn] r5843 - in 3.x/trunk/limb/macro: src tests/cases

svn at limb-project.com svn at limb-project.com
Wed May 9 02:59:01 MSD 2007


Author: pachanga
Date: 2007-05-09 02:59:01 +0400 (Wed, 09 May 2007)
New Revision: 5843
URL: http://fisheye.limb-project.com/changelog/limb/?cs=5843

Added:
   3.x/trunk/limb/macro/src/lmbMacroCompiler.class.php
   3.x/trunk/limb/macro/src/lmbMacroParser.class.php
   3.x/trunk/limb/macro/src/lmbMacroTagDictionary.class.php
   3.x/trunk/limb/macro/src/lmbMacroTagParsingState.class.php
   3.x/trunk/limb/macro/src/lmbMacroTemplateLocator.class.php
   3.x/trunk/limb/macro/src/lmbMacroTextNode.class.php
   3.x/trunk/limb/macro/tests/cases/lmbMacroCodeWriterTest.class.php
   3.x/trunk/limb/macro/tests/cases/lmbMacroTreeBuilderTest.class.php
Modified:
   3.x/trunk/limb/macro/src/lmbMacroCodeWriter.class.php
   3.x/trunk/limb/macro/src/lmbMacroNode.class.php
   3.x/trunk/limb/macro/src/lmbMacroTag.class.php
   3.x/trunk/limb/macro/src/lmbMacroTokenizerListener.interface.php
   3.x/trunk/limb/macro/src/lmbMacroTreeBuilder.class.php
Log:
-- initial versions of lmbMacroCompiler, lmbMacroParser, lmbMacroTagDictionary, lmbMacroCodeWriter, lmbMacroTextNode, lmbMacroTemplateLocator added
-- code cleanup
-- tests for lmbMacroCodeWriter, lmbMacroTreeBuilder added

Modified: 3.x/trunk/limb/macro/src/lmbMacroCodeWriter.class.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroCodeWriter.class.php	2007-05-08 21:36:03 UTC (rev 5842)
+++ 3.x/trunk/limb/macro/src/lmbMacroCodeWriter.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -83,6 +83,11 @@
     $this->switchToHTML(substr($text,0,1));
     $this->code .= $text;
   }
+  
+  function writeRaw($text)
+  {
+    $this->code .= $text;
+  }
 
   function renderCode()
   {

Added: 3.x/trunk/limb/macro/src/lmbMacroCompiler.class.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroCompiler.class.php	                        (rev 0)
+++ 3.x/trunk/limb/macro/src/lmbMacroCompiler.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Limb Web Application Framework
+ *
+ * @link http://limb-project.com
+ *
+ * @copyright  Copyright &copy; 2004-2007 BIT
+ * @license    LGPL http://www.gnu.org/copyleft/lesser.html
+ * @version    $Id: lmbMacroCompiler.class.php 5594 2007-04-10 09:00:02Z pachanga $
+ * @package    macro
+ */
+
+class lmbMacroCompiler
+{
+  /**
+  * @var lmbMacroTreeBuilder
+  */
+  protected $tree_builder;
+
+  /**
+  * @var lmbMacroConfig
+  */
+  protected $config;
+
+  /**
+  * @var lmbMacroTemplateLocator
+  */
+  protected $template_locator;
+
+  /**
+  * @var lmbMacroSourceParser
+  */
+  protected $parser;
+
+  /**
+  * @var lmbMacroTagDictionary
+  */
+  protected $tag_dictionary;
+
+
+  function __construct($config, $tag_dictionary, $template_locator)
+  {
+    $this->config = $config;
+    $this->template_locator = $template_locator;
+
+    $this->tag_dictionary = $tag_dictionary;
+    $this->tree_builder = new lmbMacroTreeBuilder($this);
+  }
+
+  function compile($file_name)
+  {
+    if(!$source_file_path = $this->template_locator->locateSourceTemplate($file_name))    
+     throw new lmbMacroException('Template source file not found', array('file_name' => $file_name));
+
+    $root_node = new lmbMacroRootNode(new lmbMacroSourceLocation($source_file_path, ''));
+
+    $this->parseTemplate($file_name, $root_node);
+
+    $root_node->prepare();
+
+    $compiled_file_path = $this->template_locator->locateCompiledTemplate($file_name);
+    $generated_code = $this->_generateTemplateCode(md5($compiled_file_path), $root_node);
+    self :: writeFile($compiled_file_path, $generated_code);
+  }
+
+  function _generateTemplateCode($prefix, $root_node)
+  {
+    $code_writer = new lmbMacroCodeWriter();
+    $code_writer->setFunctionPrefix($prefix);
+
+    $constructor_func = $code_writer->beginFunction('($root, $components)');
+    $root_node->generateConstructor($code_writer);
+    $code_writer->endFunction();
+
+    $render_func = $code_writer->beginFunction('($root, $components)');
+    $code_writer->writePHP('$template = $root;' . "\n");
+    $root_node->generate($code_writer);
+    $code_writer->endFunction();
+
+    $code_writer->writePHP('$GLOBALS[\'TemplateRender\'][$compiled_template_path] = \'' . $render_func . '\';');
+    $code_writer->writePHP('$GLOBALS[\'TemplateConstruct\'][$compiled_template_path] = \'' . $constructor_func . '\';');
+
+    return $code_writer->renderCode();
+  }
+
+  function parseTemplate($source_file_path, $root_node)
+  {
+    $parser = new lmbMacroParser($this->tree_builder,                                           
+                                 $this->config,
+                                 $this->template_locator,
+                                 $this->tag_dictionary);
+
+    $parser->parse($source_file_path, $root_node);
+  }
+
+  /**
+  * @return lmbMacroConfig
+  **/
+  function getConfig()
+  {
+    return $this->config;
+  }
+
+  /**
+  * @return lmbMacroTemplateLocator
+  **/
+  function getTemplateLocator()
+  {
+    return $this->template_locator;
+  }
+
+  /**
+  * @return lmbMacroTreeBuilder
+  **/
+  function getTreeBuilder()
+  {
+    return $this->tree_builder;
+  }
+
+  function getTagDictionary()
+  {
+    return $this->tag_dictionary;
+  }
+
+  static function writeFile($file, $data)
+  {
+    $dirname = dirname($file);    
+    lmbFs :: mkdir($dirname);
+
+    file_put_contents($file, $data);
+  }
+}
+?>

Modified: 3.x/trunk/limb/macro/src/lmbMacroNode.class.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroNode.class.php	2007-05-08 21:36:03 UTC (rev 5842)
+++ 3.x/trunk/limb/macro/src/lmbMacroNode.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -35,6 +35,11 @@
     $this->parent = $parent;
   }
   
+  function getParent()
+  {
+    return $this->parent;
+  }
+  
   function getLocationInTemplate()
   {
     return $this->location;

Added: 3.x/trunk/limb/macro/src/lmbMacroParser.class.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroParser.class.php	                        (rev 0)
+++ 3.x/trunk/limb/macro/src/lmbMacroParser.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -0,0 +1,153 @@
+<?php
+/**
+ * Limb Web Application Framework
+ *
+ * @link http://limb-project.com
+ *
+ * @copyright  Copyright &copy; 2004-2007 BIT
+ * @license    LGPL http://www.gnu.org/copyleft/lesser.html
+ * @version    $Id: lmbMacroSourceFileParser.class.php 5553 2007-04-06 09:05:17Z serega $
+ * @package    macro
+ */
+
+class lmbMacroParser implements lmbMacroTokenizerListener
+{
+  protected $active_parsing_state;
+  protected $component_parsing_state;
+  protected $literal_parsing_state;
+
+  /**
+   * @var lmbMacroConfig
+   */
+  protected $config;
+
+  /**
+   * @var lmbMacrotree_builder
+   */
+  protected $tree_builder;
+
+  /**
+   * @var lmbMacroTemplateLocator
+   */
+  protected $template_locator;
+
+  function __construct($tree_builder, $config, $template_locator, $tag_dictionary)
+  {
+    $this->tree_builder = $tree_builder;
+
+    $this->config = $config;
+    $this->template_locator = $template_locator;
+
+    $this->component_parsing_state = $this->_createComponentParsingState($tag_dictionary);
+    $this->literal_parsing_state = $this->_createLiteralParsingState();
+
+    $this->changeToComponentParsingState();
+  }
+
+  // for testing purposes
+  protected function _createComponentParsingState($tag_dictionary)
+  {
+    return new lmbMacroTagParsingState($this, $this->tree_builder, $tag_dictionary);
+  }
+
+  // for testing purposes
+  protected function _createLiteralParsingState()
+  {
+    return new lmbMacroLiteralParsingState($this, $this->tree_builder);
+  }
+
+  /**
+  * Used to parse the source template.
+  * Initially invoked by the CompileTemplate function,
+  * the first component argument being a root node.
+  */
+  function parse($file_name, $root_node)
+  {
+    $source_file_path = $this->template_locator->locateSourceTemplate($file_name);
+
+    if(empty($source_file_path))
+        throw new lmbMacroException('Template source file not found', array('file_name' => $file_name));
+
+    $tags_before_parse = $this->tree_builder->getExpectedTagCount();
+
+    $this->tree_builder->setCursor($root_node);
+
+    $this->changeToComponentParsingState();
+
+    $tokenizer = new lmbMacroTokenizer($this);
+
+    $this->setTemplateLocator($parser);
+
+    $content = $this->template_locator->readTemplateFile($source_file_path);
+
+    $tokenizer->parse($content, $source_file_path);
+
+    if($tags_before_parse != $this->tree_builder->getExpectedTagCount())
+    {
+      $location = $this->tree_builder->getExpectedTagLocation();
+      throw new lmbMacroException('Missing close tag',
+                              array('tag' => $this->tree_builder->getExpectedTag(),
+                                    'file' => $location->getFile(),
+                                    'line' => $location->getLine()));
+    }
+  }
+
+  function getActiveParsingState()
+  {
+    return $this->active_parsing_state;
+  }
+  
+  function changeToComponentParsingState()
+  {
+    $this->active_parsing_state = $this->component_parsing_state;
+  }  
+
+  function changeToLiteralParsingState($tag)
+  {
+    $this->active_parsing_state = $this->literal_parsing_state;
+    $this->active_parsing_state->setLiteralTag($tag);
+  }
+  
+  function setTemplateLocator($template_locator)
+  {
+    $this->literal_parsing_state->setTemplateLocator($template_locator);
+    $this->component_parsing_state->setTemplateLocator($template_locator);
+  }  
+
+  function startElement($tag, $attrs)
+  {
+    $this->active_parsing_state->startElement($tag, $attrs);
+  }
+
+  function endElement($tag)
+  {
+    $this->active_parsing_state->endElement($tag);
+  }
+
+  function emptyElement($tag, $attrs)
+  {
+    $this->active_parsing_state->emptyElement($tag, $attrs);
+  }
+
+  function characters($text)
+  {
+    $this->active_parsing_state->characters($text);
+  }
+
+  function unexpectedEOF($text)
+  {
+    $this->active_parsing_state->unexpectedEOF($text);
+  }
+
+  function invalidEntitySyntax($text)
+  {
+    $this->active_parsing_state->invalidEntitySyntax($text);
+  }
+
+  function invalidAttributeSyntax()
+  {
+    $this->active_parsing_state->invalidAttributeSyntax();
+  }
+}
+
+?>

Modified: 3.x/trunk/limb/macro/src/lmbMacroTag.class.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroTag.class.php	2007-05-08 21:36:03 UTC (rev 5842)
+++ 3.x/trunk/limb/macro/src/lmbMacroTag.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -33,6 +33,16 @@
     return $this->tag;
   }
   
+  function getHasClosingTag()
+  {
+    return $this->has_closing_tag;
+  }
+  
+  function setHasClosingTag($flag)
+  {
+    return $this->has_closing_tag = $flag;
+  }  
+  
   function getId()
   {
     if($this->id)

Added: 3.x/trunk/limb/macro/src/lmbMacroTagDictionary.class.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroTagDictionary.class.php	                        (rev 0)
+++ 3.x/trunk/limb/macro/src/lmbMacroTagDictionary.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Limb Web Application Framework
+ *
+ * @link http://limb-project.com
+ *
+ * @copyright  Copyright &copy; 2004-2007 BIT
+ * @license    LGPL http://www.gnu.org/copyleft/lesser.html
+ * @version    $Id$
+ * @package    macro
+ */
+
+lmb_require('limb/macro/src/lmbMacroTagInfo.class.php');
+
+class lmbMacroTagDictionary
+{
+  protected $info = array();
+
+  function register($taginfo, $file)
+  {
+    $tag_to_lower = strtolower($taginfo->getTag());
+
+    if(isset($this->info[$tag_to_lower]))
+      return;
+
+    $taginfo->setFile($file);
+    $this->info[$tag_to_lower] = $taginfo;
+  }
+
+  function findTagInfo($tag)
+  {
+    $tag = strtolower($tag);
+    if(isset($this->info[$tag]))
+      return $this->info[$tag];
+  }
+}
+?>
\ No newline at end of file

Added: 3.x/trunk/limb/macro/src/lmbMacroTagParsingState.class.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroTagParsingState.class.php	                        (rev 0)
+++ 3.x/trunk/limb/macro/src/lmbMacroTagParsingState.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Limb Web Application Framework
+ *
+ * @link http://limb-project.com
+ *
+ * @copyright  Copyright &copy; 2004-2007 BIT
+ * @license    LGPL http://www.gnu.org/copyleft/lesser.html
+ * @version    $Id: lmbMacroTagParsingState.class.php 5780 2007-04-28 13:03:26Z serega $
+ * @package    macro
+ */
+
+lmb_require('limb/macro/src/lmbMacroTokenizerListener.interface.php');
+lmb_require('limb/macro/src/lmbMacroBaseParsingState.class.php');
+
+class lmbMacroTagParsingState extends lmbMacroBaseParsingState implements lmbMacroTokenizerListener
+{
+  protected $tag_dictionary;
+
+  function __construct($parser, $tree_builder, $tag_dictionary)
+  {
+    parent :: __construct($parser, $tree_builder);
+    $this->tag_dictionary = $tag_dictionary;
+  }
+
+  function startElement($tag, $attrs)
+  {
+    $location = $this->locator->getCurrentLocation();
+
+    $lower_attributes = $this->normalizeAttributes($attrs, $location);
+
+    $tag_info = $this->tag_dictionary->findTagInfo($tag);
+
+    if($tag_info->isEndTagForbidden())
+    {
+      $tag_node = $this->buildTagNode($tag_info, $tag, $attrs, $self_closed_tag = true);
+      $tag_node->setHasClosingTag(false);
+      $this->tree_builder->pushNode($tag_node); // for cases like <%include%> we do pushNode() and popNode() here.
+      $this->tree_builder->popNode();      
+    }
+    else
+    {
+      $this->tree_builder->pushExpectedTag($tag, $location);
+      $tag_node = $this->buildTagNode($tag_info, $tag, $attrs, $self_closed_tag = false);
+      $result = $this->tree_builder->pushNode($tag_node);
+    }
+  }
+
+  function endElement($tag)
+  {
+    $tag_info = $this->tag_dictionary->getTagInfo($tag);
+    $location = $this->locator->getCurrentLocation();
+
+    if($tag_info->isEndTagForbidden())
+    {
+      throw new lmbMacroException('Closing tag forbidden',
+                              array('tag' => $tag_info->getTag(),
+                                    'file' => $location->getFile(),
+                                    'line' => $location->getLine()));
+    }
+
+    $this->tree_builder->popExpectedTag($tag, $location);
+    $this->tree_builder->popNode();
+  }
+
+  function emptyElement($tag, $attrs)
+  {
+    $location = $this->locator->getCurrentLocation();
+    $lower_attributes = $this->normalizeAttributes($attrs, $location);
+
+    $tag_info = $this->tag_dictionary->findTagInfo($tag);
+    $tag_info->load();
+
+    $tag_node = $this->buildTagNode($tag_info, $tag, $attrs, $self_closed_tag = true);
+    $tag_node->setHasClosingTag(false);
+    $this->tree_builder->pushNode($tag_node); // for cases like <%include%> we do pushNode() and popNode() here.
+    $this->tree_builder->popNode();
+  }
+  
+  /**
+  * Builds a component, adding attributes
+  * @param lmbMacroTagInfo
+  * @param string XML tag name of component
+  * @param array attributes for tag
+  * @param boolean whether the tag has contents
+  * @return lmbMacroNode
+  */
+  function buildTagNode($tag_info, $tag, $attrs, $isEmpty)
+  {
+    $tag_node = $this->_createTagNode($tag_info, $tag);
+    $tag_node->emptyClosedTag = $isEmpty;
+    $this->_addAttributesToTagNode($tag_node, $attrs);
+    return $tag_node;
+  }    
+  
+  protected function _addAttributesToTagNode($tag_node, $attrs)
+  {
+    foreach($attrs as $name => $value)
+    {
+      if($value === null)
+      {
+        $location = $this->locator->getCurrentLocation();
+        throw new lmbMacroException('Attribute should have a value',
+                              array('file' => $location->getFile(),
+                                    'line' => $location->getLine(),
+                                    'tag' => $tag_node->getTag(),
+                                    'attribute' => $name));
+      }
+      $tag_node->set($name, $value);
+    }
+  }  
+  
+  protected function _createTagNode($tag_info, $tag)
+  {
+    $class = $tag_info->getClass();
+    $tag_node = new $class($this->locator->getCurrentLocation(), $tag, $tag_info);
+    return $tag_node;
+  }    
+
+  function normalizeAttributes($attrs)
+  {
+    return array_change_key_case($attrs, CASE_LOWER);    
+  }
+
+  function characters($text)
+  {
+    $this->tree_builder->addTextNode($text);
+  }
+
+  function unexpectedEOF($text)
+  {
+    $this->tree_builder->addTextNode($text);
+  }
+
+  function invalidEntitySyntax($text)
+  {
+    $this->tree_builder->addTextNode($text);
+  }
+}
+?>
\ No newline at end of file

Added: 3.x/trunk/limb/macro/src/lmbMacroTemplateLocator.class.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroTemplateLocator.class.php	                        (rev 0)
+++ 3.x/trunk/limb/macro/src/lmbMacroTemplateLocator.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Limb Web Application Framework
+ *
+ * @link http://limb-project.com
+ *
+ * @copyright  Copyright &copy; 2004-2007 BIT
+ * @license    LGPL http://www.gnu.org/copyleft/lesser.html
+ * @version    $Id: WactDefaultTemplateLocator.class.php 5420 2007-03-29 12:45:34Z serega $
+ * @package    macro
+ */
+
+class lmbMacroTemplateLocator
+{
+  protected $config;
+  protected $templates_dir;
+
+  public function __construct($config)
+  {
+    $this->config = $config;
+    $this->templates_dir = 'templates/';//fix it
+  }
+
+  public function locateCompiledTemplate($file_name)
+  {
+    return $this->config->getCacheDir() . '/' . md5($file_name) . '.php';
+  }
+
+  public function locateSourceTemplate($file_name)
+  {    
+    return $this->templates_dir . '/' . $file_name;
+  }
+
+  public function readTemplateFile($file_name)
+  {    
+    return file_get_contents($file_name, 1);
+  }
+}
+?>
\ No newline at end of file

Added: 3.x/trunk/limb/macro/src/lmbMacroTextNode.class.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroTextNode.class.php	                        (rev 0)
+++ 3.x/trunk/limb/macro/src/lmbMacroTextNode.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Limb Web Application Framework
+ *
+ * @link http://limb-project.com
+ *
+ * @copyright  Copyright &copy; 2004-2007 BIT
+ * @license    LGPL http://www.gnu.org/copyleft/lesser.html
+ * @version    $Id: lmbMacroTextNode.class.php 5780 2007-04-28 13:03:26Z serega $
+ * @package    macro
+ */
+
+class lmbMacroTextNode extends lmbMacroNode
+{
+  protected $contents;
+
+  function __construct($location, $text)
+  {
+    parent :: __construct($location);
+    $this->contents = $text;
+  }
+
+  function generateContents($code_writer)
+  {
+    $code_writer->writeRaw($this->contents);
+    parent :: generateContents($code_writer);
+  }
+
+  function getText()
+  {
+    return $this->contents;
+  }
+}
+
+?>
\ No newline at end of file

Modified: 3.x/trunk/limb/macro/src/lmbMacroTokenizerListener.interface.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroTokenizerListener.interface.php	2007-05-08 21:36:03 UTC (rev 5842)
+++ 3.x/trunk/limb/macro/src/lmbMacroTokenizerListener.interface.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -19,6 +19,6 @@
   function unexpectedEOF($data);
   function invalidEntitySyntax($data);
   function invalidAttributeSyntax();
-  function setDocumentLocator($locator);
+  function setTemplateLocator($locator);
 }
 ?>
\ No newline at end of file

Modified: 3.x/trunk/limb/macro/src/lmbMacroTreeBuilder.class.php
===================================================================
--- 3.x/trunk/limb/macro/src/lmbMacroTreeBuilder.class.php	2007-05-08 21:36:03 UTC (rev 5842)
+++ 3.x/trunk/limb/macro/src/lmbMacroTreeBuilder.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -11,7 +11,7 @@
  */
 
 /**
-* Acts on the lmbMacroTreeRootNode in response to events within the lmbMacroSourceFileParser
+* Acts on the root lmbMacroNode in response to events within the lmbMacroParser
 *
 * When adding an open tag to the tree, call pushExpectedTag().  When closing
 * a tag, call popExpectedTag(), which ensures the tree is balanced.
@@ -21,150 +21,140 @@
 *
 * To add a node to the tree, you have the following choices.  To add a node
 * which can have children, use pushNode().  To add a terminal node use addNode(),
-* or addWactTextNode().
-*
+* or addTextNode().
 */
+
+lmb_require('limb/macro/src/lmbMacroTextNode.class.php');
+
 class lmbMacroTreeBuilder
 {
-    protected $compiler;
-    protected $component;
+  protected $compiler;
+  protected $node;
 
-    /**
-    * Stack of tags pushed onto the tree builder, may also contain components
-    * @see pushExpectedTag
-    * @see popExpectedTag
-    * @see pushCursor
-    * @var array of array($tagname, $info) or array($Component)
-    * @access private
-    */
-    var $expectedTags = array();
+  /**
+  * Stack of tags pushed onto the tree builder
+  */
+  var $expected_tags = array();
 
-    function __construct($compiler)
-    {
-      $this->compiler = $compiler;
-    }
+  function __construct($compiler)
+  {
+    $this->compiler = $compiler;
+  }
 
-    /**
-    * Returns the current component
-    */
-    function getCursor()
-    {
-      return $this->component;
-    }
+  /**
+  * Returns the current node
+  */
+  function getCursor()
+  {
+    return $this->node;
+  }
 
-    /**
-    * Sets the cursor (the current working component) of the tree builder
-    */
-    function setCursor($component)
-    {
-      $this->component = $component;
-    }
+  /**
+  * Sets the cursor (the current working node) of the tree builder
+  */
+  function setCursor($node)
+  {
+    $this->node = $node;
+  }
 
-    /**
-    * Begins a component's build phase in relation to the component tree.
-    * Adds a component to the tree, then makes that component the 'cursor'.
-    */
-    function pushNode($newComponent)
-    {
-      $this->component->addChild($newComponent);
-      $this->setCursor($newComponent);
-      return $this->component->preParse($this->compiler);
-    }
+  /**
+  * Begins a node's build phase in relation to the node tree.
+  * Adds a node to the tree, then makes that node the 'cursor'.
+  */
+  function pushNode($node)
+  {
+    $this->node->addChild($node);
+    $this->setCursor($node);
+    return $this->node->preParse($this->compiler);
+  }
 
-    /**
-    * Adds a component to the tree, without descending into it.
-    * This begins and finishes the component's composition
-    */
-    function addNode($childComponent)
-    {
-      $childComponent->preParse($this->compiler);
-      $this->component->addChild($childComponent);
-    }
+  /**
+  * Adds a node to the tree, without descending into it.
+  * This begins and finishes the node's composition
+  */
+  function addNode($node)
+  {
+    $node->preParse($this->compiler);
+    $this->node->addChild($node);
+  }
 
-    function addTextNode($text)
-    {
-      $this->addNode(new lmbMacroTextNode(null, $text));
-    }
+  function addTextNode($text)
+  {
+    $this->addNode(new lmbMacroTextNode(null, $text));
+  }
 
-    /**
-    * Ends a component's build phase in relation to the tree.
-    * Checks child server ids and moves the 'cursor' up the tree to the parent
-    * component.
-    */
-    function popNode($hasClosingTag)
-    {
-      $this->component->hasClosingTag = $hasClosingTag;
-      $this->component->checkChildrenServerIds();
-      $this->setCursor($this->component->parent);
-    }
+  /**
+  * Ends a node's build phase in relation to the tree.
+  * Checks child server ids and moves the 'cursor' up the tree to the parent
+  * node.
+  */
+  function popNode()
+  {    
+    $this->node->checkChildrenIds();
+    $this->setCursor($this->node->getParent());
+  }
 
-    /**
-    * Expects the passed tag.  Optionally $info may be passed which is info
-    * about that tag.  The parser state that calls TreeBuilder may use this info
-    * to differentiate, say, plain vs. component tags.
-    */
-    function pushExpectedTag($tag, $info = null, $location = null)
+  function pushExpectedTag($tag, $location = null)
+  {
+    array_push($this->expected_tags, array($tag, $location));
+  }
+
+  /**
+  * Sets the cursor to a new position, and pushes the old cursor onto the
+  * expected tags stack.
+  * @see popExpectedTag
+  */
+  function pushCursor($newPosition, $location)
+  {
+    // use of array() is to preserve reference from array_pop()
+    array_push($this->expected_tags, array($this->node, $location));
+    $this->setCursor($newPosition);
+  }
+
+  /**
+  * Tests the passed tag against what is expected.  Returns any info that
+  * was kept about the expected tag.
+  * If the item in the tag stack is a node, then the cursor is
+  * restored to that, and popExpectedTag is called again.
+  */
+  function popExpectedTag($tag, $location)
+  {
+    if(!$expected_tag_item = array_pop($this->expected_tags))
     {
-      array_push($this->expectedTags, array($tag, $info, $location));
+      throw new lmbMacroException('Lonely closing tag', array('tag' => $tag,
+                                                          'file' => $location->getFile(),
+                                                          'line' => $location->getLine()));
     }
 
-    /**
-    * Sets the cursor to a new position, and pushes the old cursor onto the
-    * expected tags stack.
-    * @see popExpectedTag
-    */
-    function pushCursor($newPosition, $location)
+    // if we have a node on the stack, restore the cursor to that, and
+    // pop the stack again
+    if(is_object($expected_tag_item[0]))
     {
-      // use of array() is to preserve reference from array_pop()
-      array_push($this->expectedTags, array($this->component, PARSER_TAG_IS_COMPONENT, $location));
-      $this->setCursor($newPosition);
+      $this->node = $expected_tag_item[0];
+      return $this->popExpectedTag($tag, $location);
     }
 
-    /**
-    * Tests the passed tag against what is expected.  Returns any info that
-    * was kept about the expected tag.
-    * If the item in the tag stack is a component, then the cursor is
-    * restored to that, and popExpectedTag is called again.
-    */
-    function popExpectedTag($tag, $location)
-    {
-      if(!$expectedTagItem = array_pop($this->expectedTags))
-      {
-        throw new lmbMacroException('Lonely closing tag', array('tag' => $tag,
-                                                            'file' => $location->getFile(),
-                                                            'line' => $location->getLine()));
-      }
+    $expected_tag = $expected_tag_item[0];    
+    $expected_location = $expected_tag_item[1];
 
-      // if we have a component on the stack, restore the cursor to that, and
-      // pop the stack again
-      if(is_object($expectedTagItem[0]))
-      {
-        $this->component =& $expectedTagItem[0];
-        return $this->popExpectedTag($tag, $location);
-      }
+    if(strcasecmp($expected_tag, $tag) === 0)
+      return $tag;
+ 
+    throw new lmbMacroException('Unexpected closing tag',
+                             array('file' => $location->getFile(),
+                                  'tag' => $tag,
+                                  'line' => $location->getLine(),
+                                  'expected_tag' => $expected_tag,
+                                  'expected_file' => $expected_location->getFile(),
+                                  'expected_line' => $expected_location->getLine()));
+  }
 
-      $expectedTag = $expectedTagItem[0];
-      $info = $expectedTagItem[1];
-
-      if(strcasecmp($expectedTag, $tag) !== 0)
-      {
-        throw new lmbMacroException('Unexpected closing tag',
-                                 array('file' => $location->getFile(),
-                                      'tag' => $tag,
-                                      'line' => $location->getLine(),
-                                      'ExpectTag' => $expectedTag,
-                                      'ExpectTagFile' => $expectedTagItem[2]->getFile(),
-                                      'ExpectedTagLine' => $expectedTagItem[2]->getLine()));
-      }
-      return $info;
-    }
-
   /**
   * Return the size of the expected tags stack
   */
   function getExpectedTagCount()
   {
-    return count($this->expectedTags);
+    return count($this->expected_tags);
   }
 
   /**
@@ -172,16 +162,16 @@
   */
   function getExpectedTag()
   {
-    // Returns the tagname of the first non-component item on the stack
-    $item = end($this->expectedTags);
+    // Returns the tagname of the first non-node item on the stack
+    $item = end($this->expected_tags);
     while($item && !is_string($item[0]))
-      $item = prev($this->expectedTags);
+      $item = prev($this->expected_tags);
     return $item ? $item[0] : false;
   }
 
   function getExpectedTagLocation()
   {
-    $item = end($this->expectedTags);
+    $item = end($this->expected_tags);
     return $item[2];
   }
 }

Added: 3.x/trunk/limb/macro/tests/cases/lmbMacroCodeWriterTest.class.php
===================================================================
--- 3.x/trunk/limb/macro/tests/cases/lmbMacroCodeWriterTest.class.php	                        (rev 0)
+++ 3.x/trunk/limb/macro/tests/cases/lmbMacroCodeWriterTest.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Limb Web Application Framework
+ *
+ * @link http://limb-project.com
+ *
+ * @copyright  Copyright &copy; 2004-2007 BIT
+ * @license    LGPL http://www.gnu.org/copyleft/lesser.html
+ * @version    $Id$
+ * @package    macro
+ */
+
+lmb_require('limb/macro/src/lmbMacroCodeWriter.class.php'); 
+
+class lmbMacroCodeWriterTest extends UnitTestCase
+{
+  protected $writer;
+
+  function setUp()
+  {
+    $this->writer = new lmbMacroCodeWriter();
+  }
+
+  function testGetCode()
+  {
+    $this->assertEqual($this->writer->renderCode(),'');
+  }
+
+  function testGetSetCode()
+  {
+    $this->writer->setCode($code = 'code');
+    $this->assertEqual($code, $this->writer->getCode());
+  }
+
+  function testWritePHP()
+  {
+    $this->writer->writePHP('echo ("Hello World!");');
+    $this->assertEqual($this->writer->renderCode(),'<?php echo ("Hello World!"); ?>');
+  }
+
+  function testWriteHTML()
+  {
+    $this->writer->writeHTML('<p>Hello World!</p>');
+    $this->assertEqual($this->writer->renderCode(),'<p>Hello World!</p>');
+  }
+
+  function testSwithBetweenPHPAndHTML()
+  {
+    $this->writer->writePHP('echo ("Hello World!");');
+    $this->writer->writeHTML('<p>Hello World!</p>');
+    $this->writer->writePHP('echo ("Hello World!");');
+    $this->assertEqual($this->writer->renderCode(),
+                       '<?php echo ("Hello World!"); ?><p>Hello World!</p><?php echo ("Hello World!"); ?>');
+  }
+
+  function testRegisterInclude()
+  {
+    $this->writer->registerInclude('test.php');
+    $this->assertEqual($this->writer->renderCode(),'<?php '."require_once('test.php');\n".'?>');
+  }
+
+  function testReset()
+  {
+    $this->writer->writePHP('echo ("Hello World!");');
+    $this->writer->registerInclude('test.php');
+    $this->writer->reset();
+    $this->assertEqual($this->writer->renderCode(), '');
+  }
+
+  function testBeginFunction()
+  {
+    $params = '($a,$b,$c)';
+    $this->writer->beginFunction($params);
+    $this->assertEqual($this->writer->renderCode(),'<?php function tpl1'.$params ." {\n ?>");
+  }
+
+  function testEndFunction()
+  {
+    $this->writer->endFunction();
+    $this->assertEqual($this->writer->renderCode(),'<?php '." }\n".' ?>');
+  }
+
+  function testSetFunctionPrefix()
+  {
+    $this->writer->setFunctionPrefix('Test');
+    $params = '($a,$b,$c)';
+    $this->writer->beginFunction($params);
+    $this->assertEqual($this->writer->renderCode(),'<?php function tplTest1'.$params ." {\n ?>");
+  }
+
+  function testGetTempVariable()
+  {
+    $var = $this->writer->getTempVariable();
+    $this->assertWantedPattern('/[a-z][a-z0-9]*/i', $var);
+  }
+
+  function testGetSecondTempVariable()
+  {
+    $A = $this->writer->getTempVariable();
+    $B = $this->writer->getTempVariable();
+    $this->assertNotEqual($A, $B);
+  }
+
+  function testGetTempVariablesMany()
+  {
+    for ($i = 1; $i <= 30; $i++)
+    {
+      $var = $this->writer->getTempVariable();
+      $this->assertWantedPattern('/[a-z][a-z0-9]*/i', $var);
+    }
+  }
+}
+?>
\ No newline at end of file

Added: 3.x/trunk/limb/macro/tests/cases/lmbMacroTreeBuilderTest.class.php
===================================================================
--- 3.x/trunk/limb/macro/tests/cases/lmbMacroTreeBuilderTest.class.php	                        (rev 0)
+++ 3.x/trunk/limb/macro/tests/cases/lmbMacroTreeBuilderTest.class.php	2007-05-08 22:59:01 UTC (rev 5843)
@@ -0,0 +1,237 @@
+<?php
+/**
+ * Limb Web Application Framework
+ *
+ * @link http://limb-project.com
+ *
+ * @copyright  Copyright &copy; 2004-2007 BIT
+ * @license    LGPL http://www.gnu.org/copyleft/lesser.html
+ * @version    $Id: lmbMacroTreeBuilderTest.class.php 5783 2007-04-29 07:04:40Z serega $
+ * @package    core
+ */
+
+lmb_require('limb/macro/src/lmbMacroCompiler.class.php');
+lmb_require('limb/macro/src/lmbMacroTagDictionary.class.php');
+lmb_require('limb/macro/src/lmbMacroSourceLocation.class.php');
+lmb_require('limb/macro/src/lmbMacroTagInfo.class.php');
+lmb_require('limb/macro/src/lmbMacroNode.class.php');
+lmb_require('limb/macro/src/lmbMacroTag.class.php');
+lmb_require('limb/macro/src/lmbMacroTreeBuilder.class.php');
+
+Mock::generate('lmbMacroNode', 'MockMacroNode');
+Mock::generate('lmbMacroCompiler', 'MockMacroCompiler');
+
+class lmbMacroTreeBuilderTest extends UnitTestCase
+{
+  protected $compiler;
+  protected $tree_builder;
+  protected $component;
+  protected $tag_dictionary;
+
+  function setUp()
+  {
+    $this->compiler = new MockMacroCompiler();
+    $this->tag_dictionary = new lmbMacroTagDictionary();
+    $this->component = new lmbMacroTag(new lmbMacroSourceLocation('my_file', 1),
+                                           $tag_name = 'my_tag',
+                                           new lmbMacroTagInfo($tag_name, 'MyTagClass'));
+    $this->tree_builder = new lmbMacroTreeBuilder($this->compiler);
+    $this->tree_builder->setCursor($this->component);
+  }
+
+  function testPushNodeMakedPushedNodeCurrentCursor()
+  {
+    $this->assertEqual($this->component->getChildren(), array());
+    $this->assertReference($this->component, $this->tree_builder->getCursor());
+
+    $child_component = new MockMacroNode();
+    $child_component->expectOnce('preParse', array($this->compiler));
+
+    $this->tree_builder->pushNode($child_component);
+
+    $this->assertReference($child_component, $this->tree_builder->getCursor());
+    $children = $this->component->getChildren();
+    $this->assertReference($children[0], $child_component);
+  }
+
+  function testAddNodeDontChangeCursor()
+  {
+    $this->assertEqual($this->component->getChildren(), array());
+    $this->assertReference($this->component, $this->tree_builder->getCursor());
+
+    $child_component = new MockMacroNode();
+    $child_component->expectOnce('preParse');
+
+    $this->tree_builder->addNode($child_component);
+
+    $this->assertReference($this->component, $this->tree_builder->getCursor());
+    $children = $this->component->getChildren();
+    $this->assertReference($children[0], $child_component);
+  }
+
+  function testAddlmbMacroTextNode()
+  {
+    $this->assertReference($this->component, $this->tree_builder->getCursor());
+
+    $this->tree_builder->addTextNode('text');
+
+    $this->assertReference($this->component, $this->tree_builder->getCursor());
+    $children = $this->component->getChildren();
+    $this->assertEqual(sizeof($children), 1);
+    $this->assertIsA($children[0], 'lmbMacroTextNode');
+    $this->assertEqual($children[0]->getText(), 'text');
+  }
+
+  function testPopNodeChangeCursorToParent()
+  {
+    $this->assertReference($this->component, $this->tree_builder->getCursor());
+
+    $parent_component = new lmbMacroNode();
+    $this->component->setParent($parent_component);
+
+    $this->tree_builder->popNode();    
+
+    $this->assertTrue($this->component->getHasClosingTag());
+    $this->assertReference($parent_component, $this->tree_builder->getCursor());
+  }
+
+  function testPopExpectedTagWithoutAnyExpected()
+  {
+    $location = new lmbMacroSourceLocation('my_file', 10);
+
+    try
+    {
+      $this->tree_builder->popExpectedTag('tag2', $location);
+    }
+    catch(lmbMacroException $e)
+    {
+      $this->assertWantedPattern('/Lonely closing tag/', $e->getMessage());
+      $params = $e->getParams();
+      $this->assertEqual($params['file'], 'my_file');
+      $this->assertEqual($params['line'], 10);
+      $this->assertEqual($params['tag'], 'tag2');
+    }
+  }
+
+  function testPairPushAndPopTheSameTagWorksOk()
+  {
+    $whatever_location = new lmbMacroSourceLocation('my_file', 1);
+
+    $open_location = new lmbMacroSourceLocation('my_file', 10);
+    $close_location = new lmbMacroSourceLocation('my_file', 12);
+
+    $this->tree_builder->pushExpectedTag('other_tag', $whatever_location);
+    $this->tree_builder->pushExpectedTag('tag', $open_location);
+    $this->tree_builder->popExpectedTag('tag', $close_location);
+
+    $this->assertEqual($this->tree_builder->getExpectedTagCount(), 1);
+    $this->assertEqual($this->tree_builder->getExpectedTag(), 'other_tag');
+  }
+
+  function testPopTagThrowsExceptionForNonClosedTags()
+  {
+    $first_location = new lmbMacroSourceLocation('my_file', 1);
+    $open_location = new lmbMacroSourceLocation('my_file', 10);
+    $second_location = new lmbMacroSourceLocation('my_file', 11);
+    $close_location = new lmbMacroSourceLocation('my_file', 12);
+
+    $this->tree_builder->pushExpectedTag('first_tag', $first_location);
+    $this->tree_builder->pushExpectedTag('our_tag', $open_location);
+    $this->tree_builder->pushExpectedTag('plain_tag', $second_location);
+
+    try
+    {
+      $this->tree_builder->popExpectedTag('our_tag', $close_location);
+    }
+    catch(lmbMacroException $e)
+    {
+      $this->assertWantedPattern('/Unexpected closing tag/', $e->getMessage());
+      $params = $e->getParams();
+      $this->assertEqual($params['file'], 'my_file');
+      $this->assertEqual($params['line'], 12);
+      $this->assertEqual($params['tag'], 'our_tag');
+      $this->assertEqual($params['expected_tag'], 'plain_tag');
+      $this->assertEqual($params['expected_file'], 'my_file');
+      $this->assertEqual($params['expected_line'], 11);         
+    }
+  }
+
+  function testPushCursor()
+  {
+    // This test is essentially a test of the functionality that enables the
+    // <%wrap%> implementation.
+    // Briefly:
+    // 	(1) A tree is set up
+    //  (2) A new cursor is pushed
+    //  (3) New components added should appear under the tree
+    //	(4) When the parser pops the tag at which the cursor was pushed
+    //      the cursor returns where it was before step (2)
+    //  (5) New components added should appear under this orig. point
+
+    $root = new lmbMacroNode();
+    $InsertionPoint = new lmbMacroNode();
+    $child1 = new lmbMacroNode();
+    $child2 = new lmbMacroNode();
+
+    // set up an open tag at root
+    $this->tree_builder->setCursor($root);
+    $this->tree_builder->pushExpectedTag('tag', new lmbMacroSourceLocation('my_file', 10));
+
+    // add some content to the tree
+    $this->tree_builder->pushNode($InsertionPoint);
+    $this->tree_builder->popNode();
+
+    // make sure the tree is: Root --child--> InsertionPoint with cursor
+    // at Root and open 'tag'
+    $this->assertReference($this->tree_builder->getCursor(), $root);
+    $this->assertReference($InsertionPoint->getParent(), $root);
+    $this->assertEqual($this->tree_builder->getExpectedTag(), 'tag');
+
+    // push InsertionPoint as cursor, and add another node to the tree
+    $this->tree_builder->pushCursor($InsertionPoint, new lmbMacroSourceLocation('my_file', 15));
+    $this->tree_builder->pushNode($child1);
+    $this->tree_builder->popNode();
+
+    // make sure cursor is at InsertionPoint, and new node is child of InsertionPoint
+    $this->assertReference($this->tree_builder->getCursor(), $InsertionPoint);
+    $this->assertReference($child1->getParent(), $InsertionPoint);
+
+    // now the parser gets '</tag>', and then more content
+    // so we pop 'tag' (should restore orig cursor), and add a new node
+    $this->tree_builder->popExpectedTag('tag', new lmbMacroSourceLocation('my_file', 16));
+    $this->tree_builder->pushNode($child2);
+    $this->tree_builder->popNode();
+
+    // the new node should be a child of Root, not InsertionPoint
+    $this->assertReference($this->tree_builder->getCursor(), $root);
+    $this->assertReference($child2->getParent(), $root);
+  }
+
+  function testPushAndPopExpectedTagsWithPushCursor()
+  {
+    $new_cursor = new lmbMacroNode();
+
+    $this->tree_builder->pushExpectedTag('tag1', new lmbMacroSourceLocation('my_file', 10));
+
+    // push a new cursor
+    $this->tree_builder->pushCursor($new_cursor, new lmbMacroSourceLocation('my_file', 12));
+    $this->assertReference($this->tree_builder->getCursor(), $new_cursor);
+
+    $this->tree_builder->pushExpectedTag('tag2', new lmbMacroSourceLocation('my_file', 13));
+
+    $this->assertEqual($this->tree_builder->getExpectedTagCount(), 3);
+
+    $this->assertEqual($this->tree_builder->getExpectedTag(), 'tag2');
+    $this->assertEqual($this->tree_builder->popExpectedTag('tag2', new lmbMacroSourceLocation('my_file', 15)), 'tag2');
+    $this->assertEqual($this->tree_builder->getExpectedTagCount(), 2);
+
+    // getting expected tag should skip the cursor
+    $this->assertEqual($this->tree_builder->getExpectedTag(), 'tag1');
+
+    // popping the next tag should restore the cursor to the original
+    $this->assertEqual($this->tree_builder->popExpectedTag('tag1', new lmbMacroSourceLocation('my_file', 17)), 'tag1');
+    $this->assertReference($this->tree_builder->getCursor(), $this->component);
+    $this->assertEqual($this->tree_builder->getExpectedTagCount(), 0);
+  }
+}
+?>
\ No newline at end of file



More information about the limb-svn mailing list