added a slice filter
authorFabien Potencier <fabien.potencier@gmail.com>
Sat, 14 Jan 2012 07:48:59 +0000 (08:48 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Sat, 14 Jan 2012 07:49:51 +0000 (08:49 +0100)
CHANGELOG
doc/filters/slice.rst [new file with mode: 0644]
lib/Twig/ExpressionParser.php
lib/Twig/Extension/Core.php
test/Twig/Tests/Fixtures/filters/slice.test [new file with mode: 0644]

index 0b4a7ad..75cd2e6 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,7 @@
 * 1.6.0-DEV
 
+ * added slice notation support for the [] operator (syntactic sugar for the slice operator)
+ * added a slice filter
  * added string support for the reverse filter
  * fixed the empty test and the length filter for Twig_Markup instances
  * added a date function to ease date comparison
diff --git a/doc/filters/slice.rst b/doc/filters/slice.rst
new file mode 100644 (file)
index 0000000..80a4293
--- /dev/null
@@ -0,0 +1,57 @@
+``slice``
+===========
+
+.. versionadded:: 1.6
+    The slice filter was added in Twig 1.6.
+
+The ``slice`` filter extracts a slice of a sequence, a mapping, or a string:
+
+.. code-block:: jinja
+
+    {% for i in [1, 2, 3, 4]|slice(1, 2) %}
+        {# will iterate over 2 and 3 #}
+    {% endfor %}
+
+    {{ '1234'|slice(1, 2) }}
+
+    {# outputs 23 #}
+
+You can use any valid expression for both the start and the length:
+
+.. code-block:: jinja
+
+    {% for i in [1, 2, 3, 4]|slice(start, length) %}
+        {# ... #}
+    {% endfor %}
+
+As syntactic sugar, you can also use the ``[]`` notation:
+
+.. code-block:: jinja
+
+    {% for i in [1, 2, 3, 4][start:length] %}
+        {# ... #}
+    {% endfor %}
+
+    {{ '1234'[1:2] }}
+
+The ``slice`` filter works as the `array_slice`_ PHP function for arrays and
+`substr`_ for strings.
+
+If the start is non-negative, the sequence will start at that start in the
+variable. If start is negative, the sequence will start that far from the end
+of the variable.
+
+If length is given and is positive, then the sequence will have up to that
+many elements in it. If the variable is shorter than the length, then only the
+available variable elements will be present. If length is given and is
+negative then the sequence will stop that many elements from the end of the
+variable. If it is omitted, then the sequence will have everything from offset
+up until the end of the variable.
+
+.. note::
+
+    It also works with objects implementing the `Traversable`_ interface.
+
+.. _`Traversable`: http://php.net/manual/en/class.traversable.php
+.. _`array_slice`: http://php.net/array_slice
+.. _`substr`:      http://php.net/substr
index 0e6a8df..bbe5d97 100644 (file)
@@ -320,12 +320,13 @@ class Twig_ExpressionParser
 
     public function parseSubscriptExpression($node)
     {
-        $token = $this->parser->getStream()->next();
+        $stream = $this->parser->getStream();
+        $token = $stream->next();
         $lineno = $token->getLine();
         $arguments = new Twig_Node();
         $type = Twig_TemplateInterface::ANY_CALL;
         if ($token->getValue() == '.') {
-            $token = $this->parser->getStream()->next();
+            $token = $stream->next();
             if (
                 $token->getType() == Twig_Token::NAME_TYPE
                 ||
@@ -335,7 +336,7 @@ class Twig_ExpressionParser
             ) {
                 $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
 
-                if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
+                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
                     $type = Twig_TemplateInterface::METHOD_CALL;
                     $arguments = $this->parseArguments();
                 } else {
@@ -348,7 +349,21 @@ class Twig_ExpressionParser
             $type = Twig_TemplateInterface::ARRAY_CALL;
 
             $arg = $this->parseExpression();
-            $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ']');
+
+            // slice?
+            if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
+                $stream->next();
+
+                $class = $this->getFilterNodeClass('slice');
+                $arguments = new Twig_Node(array($arg, $this->parseExpression()));
+                $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
+
+                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
+
+                return $filter;
+            }
+
+            $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
         }
 
         return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
index 4dfd8a0..886539e 100644 (file)
@@ -126,6 +126,7 @@ class Twig_Extension_Core extends Twig_Extension
             // string/array filters
             'reverse' => new Twig_Filter_Function('twig_reverse_filter', array('needs_environment' => true)),
             'length'  => new Twig_Filter_Function('twig_length_filter', array('needs_environment' => true)),
+            'slice'   => new Twig_Filter_Function('twig_slice', array('needs_environment' => true)),
 
             // iteration and runtime
             'default' => new Twig_Filter_Node('Twig_Node_Expression_Filter_Default'),
@@ -495,6 +496,35 @@ function twig_array_merge($arr1, $arr2)
 }
 
 /**
+ * Slices a variable.
+ *
+ * @param Twig_Environment $env    A Twig_Environment instance
+ * @param mixed            $item   A variable
+ * @param integer          $start  Start of the slice
+ * @param integer          $length Size of the slice
+ *
+ * @return mixed The sliced variable
+ */
+function twig_slice(Twig_Environment $env, $item, $start, $length = null)
+{
+    if ($item instanceof Traversable) {
+        $item = iterator_to_array($item, false);
+    }
+
+    if (is_array($item)) {
+        return array_slice($item, $start, $length);
+    }
+
+    $item = (string) $item;
+
+    if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) {
+        return mb_substr($item, $start, $length, $charset);
+    }
+
+    return substr($item, $start, $length);
+}
+
+/**
  * Joins the values to a string.
  *
  * The separator between elements is an empty string per default, you can define it with the optional parameter.
diff --git a/test/Twig/Tests/Fixtures/filters/slice.test b/test/Twig/Tests/Fixtures/filters/slice.test
new file mode 100644 (file)
index 0000000..da9861f
--- /dev/null
@@ -0,0 +1,24 @@
+--TEST--
+"slice" filter
+--TEMPLATE--
+{{ [1, 2, 3, 4][1:2]|join('')}}
+{{ {a: 1, b: 2, c: 3, d: 4}[1:2]|join('') }}
+{{ [1, 2, 3, 4][start:length]|join('') }}
+{{ [1, 2, 3, 4]|slice(1, 2)|join('') }}
+{{ {a: 1, b: 2, c: 3, d: 4}|slice(1, 2)|join('') }}
+{{ '1234'|slice(1, 2) }}
+{{ '1234'[1:2] }}
+{{ arr|slice(1, 2)|join('') }}
+{{ arr[1:2]|join('') }}
+--DATA--
+return array('start' => 1, 'length' => 2, 'arr' => new ArrayObject(array(1, 2, 3, 4)))
+--EXPECT--
+23
+23
+23
+23
+23
+23
+23
+23
+23