From d9dc813dfd64119815edc4ec533528de2ef4c3d6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 14 Jan 2012 08:48:59 +0100 Subject: [PATCH] added a slice filter --- CHANGELOG | 2 + doc/filters/slice.rst | 57 +++++++++++++++++++++++++++ lib/Twig/ExpressionParser.php | 23 +++++++++-- lib/Twig/Extension/Core.php | 30 ++++++++++++++ test/Twig/Tests/Fixtures/filters/slice.test | 24 +++++++++++ 5 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 doc/filters/slice.rst create mode 100644 test/Twig/Tests/Fixtures/filters/slice.test diff --git a/CHANGELOG b/CHANGELOG index 0b4a7ad..75cd2e6 100644 --- 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 index 0000000..80a4293 --- /dev/null +++ b/doc/filters/slice.rst @@ -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 diff --git a/lib/Twig/ExpressionParser.php b/lib/Twig/ExpressionParser.php index 0e6a8df..bbe5d97 100644 --- a/lib/Twig/ExpressionParser.php +++ b/lib/Twig/ExpressionParser.php @@ -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); diff --git a/lib/Twig/Extension/Core.php b/lib/Twig/Extension/Core.php index 4dfd8a0..886539e 100644 --- a/lib/Twig/Extension/Core.php +++ b/lib/Twig/Extension/Core.php @@ -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 index 0000000..da9861f --- /dev/null +++ b/test/Twig/Tests/Fixtures/filters/slice.test @@ -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 -- 1.7.2.5