소스 검색

Add standalone line whitespace handling to Parser.

Justin Hileman 12 년 전
부모
커밋
8f5be9e470
1개의 변경된 파일135개의 추가작업 그리고 30개의 파일을 삭제
  1. 135 30
      src/Mustache/Parser.php

+ 135 - 30
src/Mustache/Parser.php

@@ -16,6 +16,8 @@
  */
 class Mustache_Parser
 {
+    private $lineNum;
+    private $lineTokens;
 
     /**
      * Process an array of Mustache tokens and convert them into a parse tree.
@@ -26,6 +28,9 @@ class Mustache_Parser
      */
     public function parse(array $tokens = array())
     {
+        $this->lineNum    = -1;
+        $this->lineTokens = 0;
+
         return $this->buildTree($tokens);
     }
 
@@ -46,38 +51,62 @@ class Mustache_Parser
         while (!empty($tokens)) {
             $token = array_shift($tokens);
 
-            if ($token === null) {
-                continue;
+            if ($token[Mustache_Tokenizer::LINE] === $this->lineNum) {
+                $this->lineTokens++;
             } else {
-                switch ($token[Mustache_Tokenizer::TYPE]) {
-                    case Mustache_Tokenizer::T_SECTION:
-                    case Mustache_Tokenizer::T_INVERTED:
-                        $nodes[] = $this->buildTree($tokens, $token);
-                        break;
-
-                    case Mustache_Tokenizer::T_END_SECTION:
-                        if (!isset($parent)) {
-                            $msg = sprintf('Unexpected closing tag: /%s', $token[Mustache_Tokenizer::NAME]);
-                            throw new Mustache_Exception_SyntaxException($msg, $token);
-                        }
-
-                        if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) {
-                            $msg = sprintf('Nesting error: %s vs. %s', $parent[Mustache_Tokenizer::NAME], $token[Mustache_Tokenizer::NAME]);
-                            throw new Mustache_Exception_SyntaxException($msg, $token);
-                        }
-
-                        $parent[Mustache_Tokenizer::END]   = $token[Mustache_Tokenizer::INDEX];
-                        $parent[Mustache_Tokenizer::NODES] = $nodes;
-
-                        return $parent;
-                        break;
-
-                    default:
-                        $nodes[] = $token;
-                        break;
-                }
+                $this->lineNum    = $token[Mustache_Tokenizer::LINE];
+                $this->lineTokens = 0;
+            }
+
+            switch ($token[Mustache_Tokenizer::TYPE]) {
+                case Mustache_Tokenizer::T_DELIM_CHANGE:
+                    $this->clearStandaloneLines($nodes, $tokens);
+                    break;
+
+                case Mustache_Tokenizer::T_SECTION:
+                case Mustache_Tokenizer::T_INVERTED:
+                    $this->clearStandaloneLines($nodes, $tokens);
+                    $nodes[] = $this->buildTree($tokens, $token);
+                    break;
+
+                case Mustache_Tokenizer::T_END_SECTION:
+                    if (!isset($parent)) {
+                        $msg = sprintf('Unexpected closing tag: /%s', $token[Mustache_Tokenizer::NAME]);
+                        throw new Mustache_Exception_SyntaxException($msg, $token);
+                    }
+
+                    if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) {
+                        $msg = sprintf('Nesting error: %s vs. %s', $parent[Mustache_Tokenizer::NAME], $token[Mustache_Tokenizer::NAME]);
+                        throw new Mustache_Exception_SyntaxException($msg, $token);
+                    }
+
+                    $this->clearStandaloneLines($nodes, $tokens);
+                    $parent[Mustache_Tokenizer::END]   = $token[Mustache_Tokenizer::INDEX];
+                    $parent[Mustache_Tokenizer::NODES] = $nodes;
+
+                    return $parent;
+                    break;
+
+                case Mustache_Tokenizer::T_PARTIAL:
+                case Mustache_Tokenizer::T_PARTIAL_2:
+                    // store the whitespace prefix for laters!
+                    if ($indent = $this->clearStandaloneLines($nodes, $tokens)) {
+                        $token[Mustache_Tokenizer::INDENT] = $indent[Mustache_Tokenizer::VALUE];
+                    }
+                    $nodes[] = $token;
+                    break;
+
+                case Mustache_Tokenizer::T_PRAGMA:
+                case Mustache_Tokenizer::T_COMMENT:
+                    $this->clearStandaloneLines($nodes, $tokens);
+                    $nodes[] = $token;
+                    break;
+
+                default:
+                    $nodes[] = $token;
+                    break;
             }
-        };
+        }
 
         if (isset($parent)) {
             $msg = sprintf('Missing closing tag: %s', $parent[Mustache_Tokenizer::NAME]);
@@ -86,4 +115,80 @@ class Mustache_Parser
 
         return $nodes;
     }
+
+    /**
+     * Clear standalone line tokens.
+     *
+     * Returns a whitespace token for indenting partials, if applicable.
+     *
+     * @param array  $nodes  Parsed nodes.
+     * @param array  $tokens Tokens to be parsed.
+     *
+     * @return array Resulting indent token, if any.
+     */
+    private function clearStandaloneLines(array &$nodes, array &$tokens)
+    {
+        if ($this->lineTokens > 1) {
+            // this is the third or later node on this line, so it can't be standalone
+            return;
+        }
+
+        $prev = null;
+        if ($this->lineTokens === 1) {
+            // this is the second node on this line, so it can't be standalone
+            // unless the previous node is whitespace.
+            if ($prev = end($nodes)) {
+                if (!$this->tokenIsWhitespace($prev)) {
+                    return;
+                }
+            }
+        }
+
+        $next = null;
+        if ($next = reset($tokens)) {
+            // If we're on a new line, bail.
+            if ($next[Mustache_Tokenizer::LINE] !== $this->lineNum) {
+                return;
+            }
+
+            // If the next token isn't whitespace, bail.
+            if (!$this->tokenIsWhitespace($next)) {
+                return;
+            }
+
+            if (count($tokens) !== 1) {
+                // Unless it's the last token in the template, the next token
+                // must end in newline for this to be standalone.
+                if (substr($next[Mustache_Tokenizer::VALUE], -1) !== "\n") {
+                    return;
+                }
+            }
+
+            // Discard the whitespace suffix
+            array_shift($tokens);
+        }
+
+        if ($prev) {
+            // Return the whitespace prefix, if any
+            return array_pop($nodes);
+        }
+    }
+
+    /**
+     * Check whether token is a whitespace token.
+     *
+     * True if token type is T_TEXT and value is all whitespace characters.
+     *
+     * @param array $token
+     *
+     * @return boolean True if token is a whitespace token
+     */
+    private function tokenIsWhitespace(array $token)
+    {
+        if ($token[Mustache_Tokenizer::TYPE] == Mustache_Tokenizer::T_TEXT) {
+            return preg_match('/^\s*$/', $token[Mustache_Tokenizer::VALUE]);
+        }
+
+        return false;
+    }
 }