diff --git a/CHANGELOG b/CHANGELOG index 6dcd2e51c..12ab6ef4e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ * 1.39.0 (2019-XX-XX) - * n/a + * added support for a new whitespace trimming option ({%~ ~%}, {{~ ~}}, {#~ ~#}) * 1.38.4 (2019-03-23) diff --git a/doc/templates.rst b/doc/templates.rst index 91db45aa5..adebd9a14 100644 --- a/doc/templates.rst +++ b/doc/templates.rst @@ -844,25 +844,28 @@ Whitespace Control .. versionadded:: 1.1 Tag level whitespace control was added in Twig 1.1. -The first newline after a template tag is removed automatically (like in PHP.) +.. versionadded:: 1.39 + Tag level Line whitespace control was added in Twig 1.39. + +The first newline after a template tag is removed automatically (like in PHP). Whitespace is not further modified by the template engine, so each whitespace (spaces, tabs, newlines etc.) is returned unchanged. -Use the ``spaceless`` tag to remove whitespace *between HTML tags*: +You can also control whitespace on a per tag level. By using the whitespace +control modifiers on your tags, you can trim leading and or trailing whitespace. -.. code-block:: jinja +Twig supports two modifiers: - {% spaceless %} -
- foo bar -
- {% endspaceless %} +* *Whitespace trimming* via the ``-`` modifier: Removes all whitespace + (including newlines); - {# output will be
foo bar
#} +* *Line whitespace trimming* via the ``~`` modifier: Removes all whitespace + (excluding newlines). Using this modifier on the right disables the default + removal of the first newline inherited from PHP. -In addition to the spaceless tag you can also control whitespace on a per tag -level. By using the whitespace control modifier on your tags, you can trim -leading and or trailing whitespace: +The modifiers can be used on either side of the tags like in ``{%-`` or ``-%}`` +and they consume all whitespace for that side of the tag. It is possible to use +the modifiers on one side of a tag or on both sides: .. code-block:: jinja @@ -871,21 +874,35 @@ leading and or trailing whitespace: {%- if true -%} {{- value -}} {%- endif -%} - {# output 'no spaces' #} -The above sample shows the default whitespace control modifier, and how you can -use it to remove whitespace around tags. Trimming space will consume all whitespace -for that side of the tag. It is possible to use whitespace trimming on one side -of a tag: - -.. code-block:: jinja - - {% set value = 'no spaces' %} -
  • {{- value }}
  • +
  • + {{ value }}
  • + {# outputs '
  • \n no spaces
  • ' #} +
  • + {{- value }}
  • {# outputs '
  • no spaces
  • ' #} +
  • + {{~ value }}
  • + {# outputs '
  • \nno spaces
  • ' #} + +.. tip:: + + In addition to the whitespace modifiers, Twig also has a ``spaceless`` filter + that removes whitespace **between HTML tags**: + + .. code-block:: jinja + + {% filter spaceless %} +
    + foo bar +
    + {% endfilter %} + + {# output will be
    foo bar
    #} + Extensions ---------- diff --git a/src/Lexer.php b/src/Lexer.php index 756f123b0..c103da0d9 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -62,6 +62,8 @@ class Lexer implements \Twig_LexerInterface 'tag_block' => ['{%', '%}'], 'tag_variable' => ['{{', '}}'], 'whitespace_trim' => '-', + 'whitespace_line_trim' => '~', + 'whitespace_line_chars' => ' \t\0\x0B', 'interpolation' => ['#{', '}'], ], $options); @@ -72,6 +74,8 @@ class Lexer implements \Twig_LexerInterface (?:'. preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1]).'\s*'. // -}}\s* '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_variable'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~}}[ \t\0\x0B]* + '|'. preg_quote($this->options['tag_variable'][1]). // }} ') }Ax', @@ -82,22 +86,27 @@ class Lexer implements \Twig_LexerInterface (?:'. preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1]).'\s*\n?'. // -%}\s*\n? '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. preg_quote($this->options['tag_block'][1]).'\n?'. // %}\n? ') }Ax', // {% endverbatim %} - 'lex_raw_data' => '{ - ('. - preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim']). // {%- + 'lex_raw_data' => '{'. + preg_quote($this->options['tag_block'][0]). // {% + '('. + $this->options['whitespace_trim']. // - '|'. - preg_quote($this->options['tag_block'][0]). // {% - ')\s*'. + $this->options['whitespace_line_trim']. // ~ + ')?\s*'. '(?:end%s)'. // endraw or endverbatim '\s*'. '(?:'. preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1]).'\s*'. // -%} '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. preg_quote($this->options['tag_block'][1]). // %} ') }sx', @@ -109,6 +118,8 @@ class Lexer implements \Twig_LexerInterface (?:'. preg_quote($this->options['whitespace_trim']).preg_quote($this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n? '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]* + '|'. preg_quote($this->options['tag_comment'][1], '#').'\n?'. // #}\n? ') }sx', @@ -121,6 +132,8 @@ class Lexer implements \Twig_LexerInterface (?:'. preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1]).'\s*'. // -%}\s* '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. preg_quote($this->options['tag_block'][1]). // %} ') }Asx', @@ -137,6 +150,8 @@ class Lexer implements \Twig_LexerInterface preg_quote($this->options['tag_comment'][0], '#'). // {# ')('. preg_quote($this->options['whitespace_trim']). // - + '|'. + preg_quote($this->options['whitespace_line_trim']). // ~ ')? }sx', 'interpolation_start' => '{'.preg_quote($this->options['interpolation'][0]).'\s*}A', @@ -240,8 +255,17 @@ class Lexer implements \Twig_LexerInterface // push the template text first $text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor); + + // trim? if (isset($this->positions[2][$this->position][0])) { - $text = rtrim($text); + if ($this->options['whitespace_trim'] === $this->positions[2][$this->position][0]) { + // whitespace_trim detected ({%-, {{- or {#-) + $text = rtrim($text); + } else { + // whitespace_line_trim detected ({%~, {{~ or {#~) + // don't trim \r and \n + $text = rtrim($text, " \t\0\x0B"); + } } $this->pushToken(Token::TEXT_TYPE, $text); $this->moveCursor($textContent.$position[0]); @@ -378,8 +402,16 @@ class Lexer implements \Twig_LexerInterface $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); $this->moveCursor($text.$match[0][0]); - if (false !== strpos($match[1][0], $this->options['whitespace_trim'])) { - $text = rtrim($text); + // trim? + if (isset($match[1][0])) { + if ($this->options['whitespace_trim'] === $match[1][0]) { + // whitespace_trim detected ({%-, {{- or {#-) + $text = rtrim($text); + } else { + // whitespace_line_trim detected ({%~, {{~ or {#~) + // don't trim \r and \n + $text = rtrim($text, " \t\0\x0B"); + } } $this->pushToken(Token::TEXT_TYPE, $text); diff --git a/test/Twig/Tests/Fixtures/tags/trim_block.test b/test/Twig/Tests/Fixtures/whitespace/trim_block.test similarity index 92% rename from test/Twig/Tests/Fixtures/tags/trim_block.test rename to test/Twig/Tests/Fixtures/whitespace/trim_block.test index f1a1310ad..346a11076 100644 --- a/test/Twig/Tests/Fixtures/tags/trim_block.test +++ b/test/Twig/Tests/Fixtures/whitespace/trim_block.test @@ -1,9 +1,6 @@ --TEST-- Whitespace trimming on tags. --TEMPLATE-- -{{ 5 * '{#-'|length }} -{{ '{{-'|length * 5 + '{%-'|length }} - Trim on control tag: {% for i in range(1, 9) -%} {{ i }} @@ -53,9 +50,6 @@ end --DATA-- return ['leading' => 'leading space', 'trailing' => 'trailing space', 'both' => 'both'] --EXPECT-- -15 -18 - Trim on control tag: 123456789 diff --git a/test/Twig/Tests/Fixtures/whitespace/trim_delimiter_as_strings.test b/test/Twig/Tests/Fixtures/whitespace/trim_delimiter_as_strings.test new file mode 100644 index 000000000..a58818dde --- /dev/null +++ b/test/Twig/Tests/Fixtures/whitespace/trim_delimiter_as_strings.test @@ -0,0 +1,10 @@ +--TEST-- +Whitespace trimming as strings. +--TEMPLATE-- +{{ 5 * '{#-'|length }} +{{ '{{-'|length * 5 + '{%-'|length }} +--DATA-- +return [] +--EXPECT-- +15 +18 diff --git a/test/Twig/Tests/Fixtures/whitespace/trim_left.test b/test/Twig/Tests/Fixtures/whitespace/trim_left.test new file mode 100644 index 000000000..75d0f8031 --- /dev/null +++ b/test/Twig/Tests/Fixtures/whitespace/trim_left.test @@ -0,0 +1,32 @@ +--TEST-- +Whitespace trimming on tags (left side). +--TEMPLATE-- +**{% if true %} +foo + + {%- endif %}** + +** + + {{- 'foo' }}** + +** + + +{#- comment #}** + +**{% verbatim %} +foo + + {%- endverbatim %}** +--DATA-- +return [] +--EXPECT-- +**foo** + +**foo** + +**** + +** +foo** diff --git a/test/Twig/Tests/Fixtures/whitespace/trim_line_left.test b/test/Twig/Tests/Fixtures/whitespace/trim_line_left.test new file mode 100644 index 000000000..e5e845f08 --- /dev/null +++ b/test/Twig/Tests/Fixtures/whitespace/trim_line_left.test @@ -0,0 +1,33 @@ +--TEST-- +Line whitespace trimming on tags (left side). +--TEMPLATE-- +**{% if true %} +foo + {%~ endif %}** + +** + {{~ 'foo' }}** + +** + {#~ comment #}** + +**{% verbatim %} +foo + + {%~ endverbatim %}** +--DATA-- +return [] +--EXPECT-- +**foo +** + +** +foo** + +** +** + +** +foo + +** diff --git a/test/Twig/Tests/Fixtures/whitespace/trim_line_right.test b/test/Twig/Tests/Fixtures/whitespace/trim_line_right.test new file mode 100644 index 000000000..e7b510c85 --- /dev/null +++ b/test/Twig/Tests/Fixtures/whitespace/trim_line_right.test @@ -0,0 +1,32 @@ +--TEST-- +Line whitespace trimming on tags (right side). +--TEMPLATE-- +**{% if true ~%} +foo{% endif %}** + +**{{ 'foo' ~}} +foo +** + +**{# comment ~#} + foo +** + +**{% verbatim ~%} + foo{% endverbatim %}** +--DATA-- +return [] +--EXPECT-- +** +foo** + +**foo +foo +** + +** + foo +** + +** + foo** diff --git a/test/Twig/Tests/Fixtures/whitespace/trim_right.test b/test/Twig/Tests/Fixtures/whitespace/trim_right.test new file mode 100644 index 000000000..0ec3b0cea --- /dev/null +++ b/test/Twig/Tests/Fixtures/whitespace/trim_right.test @@ -0,0 +1,28 @@ +--TEST-- +Whitespace trimming on tags (right side). +--TEMPLATE-- +**{% if true -%} + + foo{% endif %}** + +**{{ 'foo' -}} + +** + +**{# comment -#} + +** + +**{% verbatim -%} + +foo{% endverbatim %}** +--DATA-- +return [] +--EXPECT-- +**foo** + +**foo** + +**** + +**foo**