1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
# frozen_string_literal: true
module SafeFormatHelper
# Returns a HTML-safe String.
#
# @param [String] format is escaped via `html_escape_once`
# @param [Array<Hash>] args are escaped via `html_escape` if they are not marked as HTML-safe
#
# @example
# safe_format('See %{user_input}', user_input: '<b>bold</b>')
# # => "See <b>bold</b>"
#
# safe_format('In < hour & more')
# # => "In < hour & more"
#
# @example With +tag_pair+ support
# safe_format('Some %{open}bold%{close} text.', tag_pair(tag.strong, :open, :close))
# # => "Some <strong>bold</strong> text."
# safe_format('Some %{open}bold%{close} %{italicStart}text%{italicEnd}.',
# tag_pair(tag.strong, :open, :close),
# tag_pair(tag.i, :italicStart, :italicEnd))
# # => "Some <strong>bold</strong> <i>text</i>.
def safe_format(format, *args)
args = args.inject({}, &:merge)
# Use `Kernel.format` to avoid conflicts with ViewComponent's `format`.
Kernel.format(
ERB::Util.html_escape_once(format),
args.transform_values { |value| ERB::Util.html_escape(value) }
).html_safe
end
# Returns a Hash containing a pair of +open+ and +close+ tag parts extracted
# from HTML-safe +tag+. The values are HTML-safe.
#
# Returns an empty Hash if +tag+ is not a valid paired tag (e.g. <p>foo</p>).
# an empty Hash is returned.
#
# @param [String] html_tag is a HTML-safe output from tag helper
# @param [Symbol,Object] open_name name of opening tag
# @param [Symbol,Object] close_name name of closing tag
# @raise [ArgumentError] if +tag+ is not HTML-safe
#
# @example
# tag_pair(tag.strong, :open, :close)
# # => { open: '<strong>', close: '</strong>' }
# tag_pair(link_to('', '/'), :open, :close)
# # => { open: '<a href="/">', close: '</a>' }
def tag_pair(html_tag, open_name, close_name)
raise ArgumentError, 'Argument `tag` must be `html_safe`!' unless html_tag.html_safe?
return {} unless html_tag.start_with?('<')
# end of opening tag: <p>foo</p>
# ^
open_index = html_tag.index('>')
# start of closing tag: <p>foo</p>
# ^^
close_index = html_tag.rindex('</')
return {} unless open_index && close_index
{
open_name => html_tag[0, open_index + 1],
close_name => html_tag[close_index, html_tag.size]
}
end
end
|