actionview 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +271 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/lib/action_view.rb +98 -0
  6. data/lib/action_view/base.rb +312 -0
  7. data/lib/action_view/buffers.rb +67 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +175 -0
  11. data/lib/action_view/digestor.rb +126 -0
  12. data/lib/action_view/flows.rb +76 -0
  13. data/lib/action_view/gem_version.rb +17 -0
  14. data/lib/action_view/helpers.rb +66 -0
  15. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  16. data/lib/action_view/helpers/asset_tag_helper.rb +488 -0
  17. data/lib/action_view/helpers/asset_url_helper.rb +470 -0
  18. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  19. data/lib/action_view/helpers/cache_helper.rb +271 -0
  20. data/lib/action_view/helpers/capture_helper.rb +216 -0
  21. data/lib/action_view/helpers/controller_helper.rb +36 -0
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  24. data/lib/action_view/helpers/date_helper.rb +1200 -0
  25. data/lib/action_view/helpers/debug_helper.rb +36 -0
  26. data/lib/action_view/helpers/form_helper.rb +2569 -0
  27. data/lib/action_view/helpers/form_options_helper.rb +896 -0
  28. data/lib/action_view/helpers/form_tag_helper.rb +920 -0
  29. data/lib/action_view/helpers/javascript_helper.rb +95 -0
  30. data/lib/action_view/helpers/number_helper.rb +456 -0
  31. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +101 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +171 -0
  34. data/lib/action_view/helpers/tag_helper.rb +314 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +196 -0
  37. data/lib/action_view/helpers/tags/check_box.rb +66 -0
  38. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  42. data/lib/action_view/helpers/tags/collection_select.rb +30 -0
  43. data/lib/action_view/helpers/tags/color_field.rb +27 -0
  44. data/lib/action_view/helpers/tags/date_field.rb +15 -0
  45. data/lib/action_view/helpers/tags/date_select.rb +74 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -0
  48. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +10 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
  52. data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
  53. data/lib/action_view/helpers/tags/label.rb +81 -0
  54. data/lib/action_view/helpers/tags/month_field.rb +15 -0
  55. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +33 -0
  59. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  61. data/lib/action_view/helpers/tags/select.rb +43 -0
  62. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  64. data/lib/action_view/helpers/tags/text_field.rb +34 -0
  65. data/lib/action_view/helpers/tags/time_field.rb +15 -0
  66. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +22 -0
  68. data/lib/action_view/helpers/tags/translator.rb +39 -0
  69. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +15 -0
  71. data/lib/action_view/helpers/text_helper.rb +486 -0
  72. data/lib/action_view/helpers/translation_helper.rb +145 -0
  73. data/lib/action_view/helpers/url_helper.rb +676 -0
  74. data/lib/action_view/layouts.rb +433 -0
  75. data/lib/action_view/locale/en.yml +56 -0
  76. data/lib/action_view/log_subscriber.rb +96 -0
  77. data/lib/action_view/lookup_context.rb +316 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +95 -0
  80. data/lib/action_view/railtie.rb +105 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +108 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +563 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  85. data/lib/action_view/renderer/renderer.rb +68 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +108 -0
  88. data/lib/action_view/rendering.rb +171 -0
  89. data/lib/action_view/routing_url_for.rb +146 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +393 -0
  92. data/lib/action_view/template/error.rb +161 -0
  93. data/lib/action_view/template/handlers.rb +92 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +84 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  97. data/lib/action_view/template/handlers/html.rb +11 -0
  98. data/lib/action_view/template/handlers/raw.rb +11 -0
  99. data/lib/action_view/template/html.rb +43 -0
  100. data/lib/action_view/template/inline.rb +22 -0
  101. data/lib/action_view/template/raw_file.rb +28 -0
  102. data/lib/action_view/template/resolver.rb +394 -0
  103. data/lib/action_view/template/sources.rb +13 -0
  104. data/lib/action_view/template/sources/file.rb +17 -0
  105. data/lib/action_view/template/text.rb +35 -0
  106. data/lib/action_view/template/types.rb +57 -0
  107. data/lib/action_view/test_case.rb +300 -0
  108. data/lib/action_view/testing/resolvers.rb +67 -0
  109. data/lib/action_view/unbound_template.rb +32 -0
  110. data/lib/action_view/version.rb +10 -0
  111. data/lib/action_view/view_paths.rb +129 -0
  112. data/lib/assets/compiled/rails-ujs.js +746 -0
  113. metadata +260 -0
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/output_safety"
4
+
5
+ module ActionView #:nodoc:
6
+ # = Action View Raw Output Helper
7
+ module Helpers #:nodoc:
8
+ module OutputSafetyHelper
9
+ # This method outputs without escaping a string. Since escaping tags is
10
+ # now default, this can be used when you don't want Rails to automatically
11
+ # escape tags. This is not recommended if the data is coming from the user's
12
+ # input.
13
+ #
14
+ # For example:
15
+ #
16
+ # raw @user.name
17
+ # # => 'Jimmy <alert>Tables</alert>'
18
+ def raw(stringish)
19
+ stringish.to_s.html_safe
20
+ end
21
+
22
+ # This method returns an HTML safe string similar to what <tt>Array#join</tt>
23
+ # would return. The array is flattened, and all items, including
24
+ # the supplied separator, are HTML escaped unless they are HTML
25
+ # safe, and the returned string is marked as HTML safe.
26
+ #
27
+ # safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
28
+ # # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
29
+ #
30
+ # safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />"))
31
+ # # => "<p>foo</p><br /><p>bar</p>"
32
+ #
33
+ def safe_join(array, sep = $,)
34
+ sep = ERB::Util.unwrapped_html_escape(sep)
35
+
36
+ array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe
37
+ end
38
+
39
+ # Converts the array to a comma-separated sentence where the last element is
40
+ # joined by the connector word. This is the html_safe-aware version of
41
+ # ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
42
+ #
43
+ def to_sentence(array, options = {})
44
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
45
+
46
+ default_connectors = {
47
+ words_connector: ", ",
48
+ two_words_connector: " and ",
49
+ last_word_connector: ", and "
50
+ }
51
+ if defined?(I18n)
52
+ i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
53
+ default_connectors.merge!(i18n_connectors)
54
+ end
55
+ options = default_connectors.merge!(options)
56
+
57
+ case array.length
58
+ when 0
59
+ "".html_safe
60
+ when 1
61
+ ERB::Util.html_escape(array[0])
62
+ when 2
63
+ safe_join([array[0], array[1]], options[:two_words_connector])
64
+ else
65
+ safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]], nil)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers #:nodoc:
5
+ # = Action View Rendering
6
+ #
7
+ # Implements methods that allow rendering from a view context.
8
+ # In order to use this module, all you need is to implement
9
+ # view_renderer that returns an ActionView::Renderer object.
10
+ module RenderingHelper
11
+ # Returns the result of a render that's dictated by the options hash. The primary options are:
12
+ #
13
+ # * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>.
14
+ # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
15
+ # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
16
+ # * <tt>:plain</tt> - Renders the text passed in out. Setting the content
17
+ # type as <tt>text/plain</tt>.
18
+ # * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
19
+ # performs HTML escape on the string first. Setting the content type as
20
+ # <tt>text/html</tt>.
21
+ # * <tt>:body</tt> - Renders the text passed in, and inherits the content
22
+ # type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
23
+ # object.
24
+ #
25
+ # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
26
+ # as the locals hash.
27
+ def render(options = {}, locals = {}, &block)
28
+ case options
29
+ when Hash
30
+ in_rendering_context(options) do |renderer|
31
+ if block_given?
32
+ view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
33
+ else
34
+ view_renderer.render(self, options)
35
+ end
36
+ end
37
+ else
38
+ view_renderer.render_partial(self, partial: options, locals: locals, &block)
39
+ end
40
+ end
41
+
42
+ # Overwrites _layout_for in the context object so it supports the case a block is
43
+ # passed to a partial. Returns the contents that are yielded to a layout, given a
44
+ # name or a block.
45
+ #
46
+ # You can think of a layout as a method that is called with a block. If the user calls
47
+ # <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
48
+ # If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
49
+ #
50
+ # The user can override this default by passing a block to the layout:
51
+ #
52
+ # # The template
53
+ # <%= render layout: "my_layout" do %>
54
+ # Content
55
+ # <% end %>
56
+ #
57
+ # # The layout
58
+ # <html>
59
+ # <%= yield %>
60
+ # </html>
61
+ #
62
+ # In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
63
+ # this method returns the block that was passed in to <tt>render :layout</tt>, and the response
64
+ # would be
65
+ #
66
+ # <html>
67
+ # Content
68
+ # </html>
69
+ #
70
+ # Finally, the block can take block arguments, which can be passed in by +yield+:
71
+ #
72
+ # # The template
73
+ # <%= render layout: "my_layout" do |customer| %>
74
+ # Hello <%= customer.name %>
75
+ # <% end %>
76
+ #
77
+ # # The layout
78
+ # <html>
79
+ # <%= yield Struct.new(:name).new("David") %>
80
+ # </html>
81
+ #
82
+ # In this case, the layout would receive the block passed into <tt>render :layout</tt>,
83
+ # and the struct specified would be passed into the block as an argument. The result
84
+ # would be
85
+ #
86
+ # <html>
87
+ # Hello David
88
+ # </html>
89
+ #
90
+ def _layout_for(*args, &block)
91
+ name = args.first
92
+
93
+ if block && !name.is_a?(Symbol)
94
+ capture(*args, &block)
95
+ else
96
+ super
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails-html-sanitizer"
4
+
5
+ module ActionView
6
+ # = Action View Sanitize Helpers
7
+ module Helpers #:nodoc:
8
+ # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
9
+ # These helper methods extend Action View making them callable within your template files.
10
+ module SanitizeHelper
11
+ extend ActiveSupport::Concern
12
+ # Sanitizes HTML input, stripping all but known-safe tags and attributes.
13
+ #
14
+ # It also strips href/src attributes with unsafe protocols like
15
+ # <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
16
+ # ASCII, and hex character references to work around these protocol filters.
17
+ # All special characters will be escaped.
18
+ #
19
+ # The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
20
+ # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
21
+ #
22
+ # Custom sanitization rules can also be provided.
23
+ #
24
+ # Please note that sanitizing user-provided text does not guarantee that the
25
+ # resulting markup is valid or even well-formed.
26
+ #
27
+ # ==== Options
28
+ #
29
+ # * <tt>:tags</tt> - An array of allowed tags.
30
+ # * <tt>:attributes</tt> - An array of allowed attributes.
31
+ # * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer]
32
+ # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that
33
+ # defines custom sanitization rules. A custom scrubber takes precedence over
34
+ # custom tags and attributes.
35
+ #
36
+ # ==== Examples
37
+ #
38
+ # Normal use:
39
+ #
40
+ # <%= sanitize @comment.body %>
41
+ #
42
+ # Providing custom lists of permitted tags and attributes:
43
+ #
44
+ # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
45
+ #
46
+ # Providing a custom Rails::Html scrubber:
47
+ #
48
+ # class CommentScrubber < Rails::Html::PermitScrubber
49
+ # def initialize
50
+ # super
51
+ # self.tags = %w( form script comment blockquote )
52
+ # self.attributes = %w( style )
53
+ # end
54
+ #
55
+ # def skip_node?(node)
56
+ # node.text?
57
+ # end
58
+ # end
59
+ #
60
+ # <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
61
+ #
62
+ # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for
63
+ # documentation about Rails::Html scrubbers.
64
+ #
65
+ # Providing a custom Loofah::Scrubber:
66
+ #
67
+ # scrubber = Loofah::Scrubber.new do |node|
68
+ # node.remove if node.name == 'script'
69
+ # end
70
+ #
71
+ # <%= sanitize @comment.body, scrubber: scrubber %>
72
+ #
73
+ # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more
74
+ # information about defining custom Loofah::Scrubber objects.
75
+ #
76
+ # To set the default allowed tags or attributes across your application:
77
+ #
78
+ # # In config/application.rb
79
+ # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
80
+ # config.action_view.sanitized_allowed_attributes = ['href', 'title']
81
+ def sanitize(html, options = {})
82
+ self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
83
+ end
84
+
85
+ # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
86
+ def sanitize_css(style)
87
+ self.class.safe_list_sanitizer.sanitize_css(style)
88
+ end
89
+
90
+ # Strips all HTML tags from +html+, including comments and special characters.
91
+ #
92
+ # strip_tags("Strip <i>these</i> tags!")
93
+ # # => Strip these tags!
94
+ #
95
+ # strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
96
+ # # => Bold no more! See more here...
97
+ #
98
+ # strip_tags("<div id='top-bar'>Welcome to my website!</div>")
99
+ # # => Welcome to my website!
100
+ #
101
+ # strip_tags("> A quote from Smith & Wesson")
102
+ # # => &gt; A quote from Smith &amp; Wesson
103
+ def strip_tags(html)
104
+ self.class.full_sanitizer.sanitize(html)
105
+ end
106
+
107
+ # Strips all link tags from +html+ leaving just the link text.
108
+ #
109
+ # strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
110
+ # # => Ruby on Rails
111
+ #
112
+ # strip_links('Please e-mail me at <a href="mailto:me@email.com">me@email.com</a>.')
113
+ # # => Please e-mail me at me@email.com.
114
+ #
115
+ # strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
116
+ # # => Blog: Visit.
117
+ #
118
+ # strip_links('<<a href="https://example.org">malformed & link</a>')
119
+ # # => &lt;malformed &amp; link
120
+ def strip_links(html)
121
+ self.class.link_sanitizer.sanitize(html)
122
+ end
123
+
124
+ module ClassMethods #:nodoc:
125
+ attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
126
+
127
+ def sanitizer_vendor
128
+ Rails::Html::Sanitizer
129
+ end
130
+
131
+ def sanitized_allowed_tags
132
+ safe_list_sanitizer.allowed_tags
133
+ end
134
+
135
+ def sanitized_allowed_attributes
136
+ safe_list_sanitizer.allowed_attributes
137
+ end
138
+
139
+ # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
140
+ # any object that responds to +sanitize+.
141
+ #
142
+ # class Application < Rails::Application
143
+ # config.action_view.full_sanitizer = MySpecialSanitizer.new
144
+ # end
145
+ def full_sanitizer
146
+ @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
147
+ end
148
+
149
+ # Gets the Rails::Html::LinkSanitizer instance used by +strip_links+.
150
+ # Replace with any object that responds to +sanitize+.
151
+ #
152
+ # class Application < Rails::Application
153
+ # config.action_view.link_sanitizer = MySpecialSanitizer.new
154
+ # end
155
+ def link_sanitizer
156
+ @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
157
+ end
158
+
159
+ # Gets the Rails::Html::SafeListSanitizer instance used by sanitize and +sanitize_css+.
160
+ # Replace with any object that responds to +sanitize+.
161
+ #
162
+ # class Application < Rails::Application
163
+ # config.action_view.safe_list_sanitizer = MySpecialSanitizer.new
164
+ # end
165
+ def safe_list_sanitizer
166
+ @safe_list_sanitizer ||= sanitizer_vendor.safe_list_sanitizer.new
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,314 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/output_safety"
4
+ require "set"
5
+
6
+ module ActionView
7
+ # = Action View Tag Helpers
8
+ module Helpers #:nodoc:
9
+ # Provides methods to generate HTML tags programmatically both as a modern
10
+ # HTML5 compliant builder style and legacy XHTML compliant tags.
11
+ module TagHelper
12
+ extend ActiveSupport::Concern
13
+ include CaptureHelper
14
+ include OutputSafetyHelper
15
+
16
+ BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus autoplay checked
17
+ compact controls declare default defaultchecked
18
+ defaultmuted defaultselected defer disabled
19
+ enabled formnovalidate hidden indeterminate inert
20
+ ismap itemscope loop multiple muted nohref
21
+ noresize noshade novalidate nowrap open
22
+ pauseonexit readonly required reversed scoped
23
+ seamless selected sortable truespeed typemustmatch
24
+ visible).to_set
25
+
26
+ BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
27
+
28
+ TAG_PREFIXES = ["aria", "data", :aria, :data].to_set
29
+
30
+ PRE_CONTENT_STRINGS = Hash.new { "" }
31
+ PRE_CONTENT_STRINGS[:textarea] = "\n"
32
+ PRE_CONTENT_STRINGS["textarea"] = "\n"
33
+
34
+ class TagBuilder #:nodoc:
35
+ include CaptureHelper
36
+ include OutputSafetyHelper
37
+
38
+ VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set
39
+
40
+ def initialize(view_context)
41
+ @view_context = view_context
42
+ end
43
+
44
+ def tag_string(name, content = nil, escape_attributes: true, **options, &block)
45
+ content = @view_context.capture(self, &block) if block_given?
46
+ if VOID_ELEMENTS.include?(name) && content.nil?
47
+ "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
48
+ else
49
+ content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
50
+ end
51
+ end
52
+
53
+ def content_tag_string(name, content, options, escape = true)
54
+ tag_options = tag_options(options, escape) if options
55
+ content = ERB::Util.unwrapped_html_escape(content) if escape
56
+ "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
57
+ end
58
+
59
+ def tag_options(options, escape = true)
60
+ return if options.blank?
61
+ output = +""
62
+ sep = " "
63
+ options.each_pair do |key, value|
64
+ if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
65
+ value.each_pair do |k, v|
66
+ next if v.nil?
67
+ output << sep
68
+ output << prefix_tag_option(key, k, v, escape)
69
+ end
70
+ elsif BOOLEAN_ATTRIBUTES.include?(key)
71
+ if value
72
+ output << sep
73
+ output << boolean_tag_option(key)
74
+ end
75
+ elsif !value.nil?
76
+ output << sep
77
+ output << tag_option(key, value, escape)
78
+ end
79
+ end
80
+ output unless output.empty?
81
+ end
82
+
83
+ def boolean_tag_option(key)
84
+ %(#{key}="#{key}")
85
+ end
86
+
87
+ def tag_option(key, value, escape)
88
+ if value.is_a?(Array)
89
+ value = escape ? safe_join(value, " ") : value.join(" ")
90
+ else
91
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s.dup
92
+ end
93
+ value.gsub!('"', "&quot;")
94
+ %(#{key}="#{value}")
95
+ end
96
+
97
+ private
98
+ def prefix_tag_option(prefix, key, value, escape)
99
+ key = "#{prefix}-#{key.to_s.dasherize}"
100
+ unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
101
+ value = value.to_json
102
+ end
103
+ tag_option(key, value, escape)
104
+ end
105
+
106
+ def respond_to_missing?(*args)
107
+ true
108
+ end
109
+
110
+ def method_missing(called, *args, &block)
111
+ tag_string(called, *args, &block)
112
+ end
113
+ end
114
+
115
+ # Returns an HTML tag.
116
+ #
117
+ # === Building HTML tags
118
+ #
119
+ # Builds HTML5 compliant tags with a tag proxy. Every tag can be built with:
120
+ #
121
+ # tag.<tag name>(optional content, options)
122
+ #
123
+ # where tag name can be e.g. br, div, section, article, or any tag really.
124
+ #
125
+ # ==== Passing content
126
+ #
127
+ # Tags can pass content to embed within it:
128
+ #
129
+ # tag.h1 'All titles fit to print' # => <h1>All titles fit to print</h1>
130
+ #
131
+ # tag.div tag.p('Hello world!') # => <div><p>Hello world!</p></div>
132
+ #
133
+ # Content can also be captured with a block, which is useful in templates:
134
+ #
135
+ # <%= tag.p do %>
136
+ # The next great American novel starts here.
137
+ # <% end %>
138
+ # # => <p>The next great American novel starts here.</p>
139
+ #
140
+ # ==== Options
141
+ #
142
+ # Use symbol keyed options to add attributes to the generated tag.
143
+ #
144
+ # tag.section class: %w( kitties puppies )
145
+ # # => <section class="kitties puppies"></section>
146
+ #
147
+ # tag.section id: dom_id(@post)
148
+ # # => <section id="<generated dom id>"></section>
149
+ #
150
+ # Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+.
151
+ #
152
+ # tag.input type: 'text', disabled: true
153
+ # # => <input type="text" disabled="disabled">
154
+ #
155
+ # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
156
+ # pointing to a hash of sub-attributes.
157
+ #
158
+ # To play nicely with JavaScript conventions, sub-attributes are dasherized.
159
+ #
160
+ # tag.article data: { user_id: 123 }
161
+ # # => <article data-user-id="123"></article>
162
+ #
163
+ # Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>.
164
+ #
165
+ # Data attribute values are encoded to JSON, with the exception of strings, symbols and
166
+ # BigDecimals.
167
+ # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
168
+ # from 1.4.3.
169
+ #
170
+ # tag.div data: { city_state: %w( Chicago IL ) }
171
+ # # => <div data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]"></div>
172
+ #
173
+ # The generated attributes are escaped by default. This can be disabled using
174
+ # +escape_attributes+.
175
+ #
176
+ # tag.img src: 'open & shut.png'
177
+ # # => <img src="open &amp; shut.png">
178
+ #
179
+ # tag.img src: 'open & shut.png', escape_attributes: false
180
+ # # => <img src="open & shut.png">
181
+ #
182
+ # The tag builder respects
183
+ # {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements]
184
+ # if no content is passed, and omits closing tags for those elements.
185
+ #
186
+ # # A standard element:
187
+ # tag.div # => <div></div>
188
+ #
189
+ # # A void element:
190
+ # tag.br # => <br>
191
+ #
192
+ # === Legacy syntax
193
+ #
194
+ # The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
195
+ #
196
+ # tag(name, options = nil, open = false, escape = true)
197
+ #
198
+ # It returns an empty HTML tag of type +name+ which by default is XHTML
199
+ # compliant. Set +open+ to true to create an open tag compatible
200
+ # with HTML 4.0 and below. Add HTML attributes by passing an attributes
201
+ # hash to +options+. Set +escape+ to false to disable attribute value
202
+ # escaping.
203
+ #
204
+ # ==== Options
205
+ #
206
+ # You can use symbols or strings for the attribute names.
207
+ #
208
+ # Use +true+ with boolean attributes that can render with no value, like
209
+ # +disabled+ and +readonly+.
210
+ #
211
+ # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
212
+ # pointing to a hash of sub-attributes.
213
+ #
214
+ # ==== Examples
215
+ #
216
+ # tag("br")
217
+ # # => <br />
218
+ #
219
+ # tag("br", nil, true)
220
+ # # => <br>
221
+ #
222
+ # tag("input", type: 'text', disabled: true)
223
+ # # => <input type="text" disabled="disabled" />
224
+ #
225
+ # tag("input", type: 'text', class: ["strong", "highlight"])
226
+ # # => <input class="strong highlight" type="text" />
227
+ #
228
+ # tag("img", src: "open & shut.png")
229
+ # # => <img src="open &amp; shut.png" />
230
+ #
231
+ # tag("img", { src: "open &amp; shut.png" }, false, false)
232
+ # # => <img src="open &amp; shut.png" />
233
+ #
234
+ # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
235
+ # # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
236
+ def tag(name = nil, options = nil, open = false, escape = true)
237
+ if name.nil?
238
+ tag_builder
239
+ else
240
+ "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
241
+ end
242
+ end
243
+
244
+ # Returns an HTML block tag of type +name+ surrounding the +content+. Add
245
+ # HTML attributes by passing an attributes hash to +options+.
246
+ # Instead of passing the content as an argument, you can also use a block
247
+ # in which case, you pass your +options+ as the second parameter.
248
+ # Set escape to false to disable attribute value escaping.
249
+ # Note: this is legacy syntax, see +tag+ method description for details.
250
+ #
251
+ # ==== Options
252
+ # The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
253
+ # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
254
+ # symbols or strings for the attribute names.
255
+ #
256
+ # ==== Examples
257
+ # content_tag(:p, "Hello world!")
258
+ # # => <p>Hello world!</p>
259
+ # content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
260
+ # # => <div class="strong"><p>Hello world!</p></div>
261
+ # content_tag(:div, "Hello world!", class: ["strong", "highlight"])
262
+ # # => <div class="strong highlight">Hello world!</div>
263
+ # content_tag("select", options, multiple: true)
264
+ # # => <select multiple="multiple">...options...</select>
265
+ #
266
+ # <%= content_tag :div, class: "strong" do -%>
267
+ # Hello world!
268
+ # <% end -%>
269
+ # # => <div class="strong">Hello world!</div>
270
+ def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
271
+ if block_given?
272
+ options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
273
+ tag_builder.content_tag_string(name, capture(&block), options, escape)
274
+ else
275
+ tag_builder.content_tag_string(name, content_or_options_with_block, options, escape)
276
+ end
277
+ end
278
+
279
+ # Returns a CDATA section with the given +content+. CDATA sections
280
+ # are used to escape blocks of text containing characters which would
281
+ # otherwise be recognized as markup. CDATA sections begin with the string
282
+ # <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
283
+ #
284
+ # cdata_section("<hello world>")
285
+ # # => <![CDATA[<hello world>]]>
286
+ #
287
+ # cdata_section(File.read("hello_world.txt"))
288
+ # # => <![CDATA[<hello from a text file]]>
289
+ #
290
+ # cdata_section("hello]]>world")
291
+ # # => <![CDATA[hello]]]]><![CDATA[>world]]>
292
+ def cdata_section(content)
293
+ splitted = content.to_s.gsub(/\]\]\>/, "]]]]><![CDATA[>")
294
+ "<![CDATA[#{splitted}]]>".html_safe
295
+ end
296
+
297
+ # Returns an escaped version of +html+ without affecting existing escaped entities.
298
+ #
299
+ # escape_once("1 < 2 &amp; 3")
300
+ # # => "1 &lt; 2 &amp; 3"
301
+ #
302
+ # escape_once("&lt;&lt; Accept & Checkout")
303
+ # # => "&lt;&lt; Accept &amp; Checkout"
304
+ def escape_once(html)
305
+ ERB::Util.html_escape_once(html)
306
+ end
307
+
308
+ private
309
+ def tag_builder
310
+ @tag_builder ||= TagBuilder.new(self)
311
+ end
312
+ end
313
+ end
314
+ end