Skip to content

Commit ce0d465

Browse files
committed
Make whitespace filters Unicode-aware
Previously we were only leveraging Ruby's `String#strip` to handle the logic in these filters but that only covers ASCII whitespace. When rendering Liquid templates into HTML it would be confusing for these filters to not strip *all* whitespace. Additionally, it's helpful when trying to compare two values in, say, a Liquid conditional.
1 parent 1954a26 commit ce0d465

2 files changed

Lines changed: 52 additions & 4 deletions

File tree

lib/liquid/standardfilters.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ module StandardFilters
3636
%r{<style.*?</style>}m,
3737
)
3838
STRIP_HTML_TAGS = /<.*?>/m
39+
# Use POSIX whitespace matching so filters handle whitespace beyond Ruby String#strip's ASCII set.
40+
WHITESPACE_LEFT = /\A[[:space:]]+/
41+
WHITESPACE_RIGHT = /[[:space:]]+\z/
42+
WHITESPACE_EDGES = Regexp.union(WHITESPACE_LEFT, WHITESPACE_RIGHT)
43+
# Optimized runs regex to find 2 or more [[:space:]] OR a single [[:space:]]
44+
# that isn't already `" " `.
45+
WHITESPACE_RUNS = /([[:space:]]{2,}|[[[:space:]]&&[^ ]])/
46+
private_constant(
47+
:WHITESPACE_EDGES,
48+
:WHITESPACE_LEFT,
49+
:WHITESPACE_RIGHT,
50+
:WHITESPACE_RUNS,
51+
)
3952

4053
class << self
4154
def try_coerce_encoding(input, encoding:)
@@ -312,7 +325,7 @@ def split(input, pattern)
312325
def squish(input)
313326
return if input.nil?
314327

315-
Utils.to_s(input).strip.gsub(/\s+/, ' ')
328+
Utils.to_s(input).gsub(WHITESPACE_RUNS, ' ').strip
316329
end
317330

318331
# @liquid_public_docs
@@ -324,7 +337,7 @@ def squish(input)
324337
# @liquid_return [string]
325338
def strip(input)
326339
input = Utils.to_s(input)
327-
input.strip
340+
input.gsub(WHITESPACE_EDGES, ' ').strip
328341
end
329342

330343
# @liquid_public_docs
@@ -336,7 +349,7 @@ def strip(input)
336349
# @liquid_return [string]
337350
def lstrip(input)
338351
input = Utils.to_s(input)
339-
input.lstrip
352+
input.gsub(WHITESPACE_LEFT, ' ').lstrip
340353
end
341354

342355
# @liquid_public_docs
@@ -348,7 +361,7 @@ def lstrip(input)
348361
# @liquid_return [string]
349362
def rstrip(input)
350363
input = Utils.to_s(input)
351-
input.rstrip
364+
input.gsub(WHITESPACE_RIGHT, ' ').rstrip
352365
end
353366

354367
# @liquid_public_docs

test/integration/standard_filter_test.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,15 @@ def test_squish_filter
169169
\t boo " | squish }})).render)
170170
assert_equal("", Liquid::Template.parse('{{ nil | squish }}').render)
171171
assert_equal("", Liquid::Template.parse('{{ " " | squish }}').render)
172+
173+
unicode_spaces = "\u00A0\u202F\u2009\u2007"
174+
175+
assert_template_result(
176+
"foo bar boo",
177+
"{{ source | squish }}",
178+
{ 'source' => "#{unicode_spaces}foo\u202F\u2009bar\t\n\u2007boo#{unicode_spaces}" },
179+
)
180+
assert_template_result("\u200Bfoo\u200B", "{{ source | squish }}", { 'source' => "\u200Bfoo\u200B" })
172181
end
173182

174183
def test_escape
@@ -703,16 +712,42 @@ def test_pipes_in_string_arguments
703712
def test_strip
704713
assert_template_result('ab c', "{{ source | strip }}", { 'source' => " ab c " })
705714
assert_template_result('ab c', "{{ source | strip }}", { 'source' => " \tab c \n \t" })
715+
716+
unicode_spaces = "\u00A0\u202F\u2009\u2007"
717+
718+
assert_template_result(
719+
'ab c',
720+
"{{ source | strip }}",
721+
{ 'source' => "#{unicode_spaces}ab c#{unicode_spaces}" },
722+
)
723+
assert_template_result("a\u00A0b\u202Fc", "{{ source | strip }}", { 'source' => "a\u00A0b\u202Fc" })
724+
assert_template_result("\u200Bfoo\u200B", "{{ source | strip }}", { 'source' => "\u200Bfoo\u200B" })
706725
end
707726

708727
def test_lstrip
709728
assert_template_result('ab c ', "{{ source | lstrip }}", { 'source' => " ab c " })
710729
assert_template_result("ab c \n \t", "{{ source | lstrip }}", { 'source' => " \tab c \n \t" })
730+
731+
unicode_spaces = "\u00A0\u202F\u2009\u2007"
732+
733+
assert_template_result(
734+
"ab c#{unicode_spaces}",
735+
"{{ source | lstrip }}",
736+
{ 'source' => "#{unicode_spaces}ab c#{unicode_spaces}" },
737+
)
711738
end
712739

713740
def test_rstrip
714741
assert_template_result(" ab c", "{{ source | rstrip }}", { 'source' => " ab c " })
715742
assert_template_result(" \tab c", "{{ source | rstrip }}", { 'source' => " \tab c \n \t" })
743+
744+
unicode_spaces = "\u00A0\u202F\u2009\u2007"
745+
746+
assert_template_result(
747+
"#{unicode_spaces}ab c",
748+
"{{ source | rstrip }}",
749+
{ 'source' => "#{unicode_spaces}ab c#{unicode_spaces}" },
750+
)
716751
end
717752

718753
def test_strip_newlines

0 commit comments

Comments
 (0)