aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionview/lib')
-rw-r--r--actionview/lib/action_view.rb3
-rw-r--r--actionview/lib/action_view/base.rb23
-rw-r--r--actionview/lib/action_view/buffers.rb7
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb14
-rw-r--r--actionview/lib/action_view/digestor.rb2
-rw-r--r--actionview/lib/action_view/gem_version.rb6
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb28
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb3
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb22
-rw-r--r--actionview/lib/action_view/helpers/debug_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb100
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb180
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb58
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb163
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb17
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/datetime_field.rb12
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb68
-rw-r--r--actionview/lib/action_view/helpers/tags/placeholderable.rb34
-rw-r--r--actionview/lib/action_view/helpers/tags/search_field.rb1
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/text_area.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/text_field.rb4
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb37
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb41
-rw-r--r--actionview/lib/action_view/layouts.rb2
-rw-r--r--actionview/lib/action_view/log_subscriber.rb10
-rw-r--r--actionview/lib/action_view/lookup_context.rb14
-rw-r--r--actionview/lib/action_view/model_naming.rb2
-rw-r--r--actionview/lib/action_view/record_identifier.rb61
-rw-r--r--actionview/lib/action_view/renderer/abstract_renderer.rb6
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb114
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb11
-rw-r--r--actionview/lib/action_view/rendering.rb9
-rw-r--r--actionview/lib/action_view/routing_url_for.rb35
-rw-r--r--actionview/lib/action_view/template.rb28
-rw-r--r--actionview/lib/action_view/template/error.rb2
-rw-r--r--actionview/lib/action_view/template/handlers.rb4
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb6
-rw-r--r--actionview/lib/action_view/template/handlers/raw.rb2
-rw-r--r--actionview/lib/action_view/template/resolver.rb17
-rw-r--r--actionview/lib/action_view/template/types.rb2
-rw-r--r--actionview/lib/action_view/test_case.rb19
-rw-r--r--actionview/lib/action_view/vendor/html-scanner.rb20
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/document.rb68
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/node.rb532
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb188
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/selector.rb830
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb107
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/version.rb11
-rw-r--r--actionview/lib/action_view/view_paths.rb6
56 files changed, 713 insertions, 2272 deletions
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index 50712e0830..c3bbac27fd 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -86,7 +86,6 @@ module ActionView
super
ActionView::Helpers.eager_load!
ActionView::Template.eager_load!
- HTML.eager_load!
end
end
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 900f96255e..1feafc1094 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -10,8 +10,10 @@ require 'action_view/lookup_context'
module ActionView #:nodoc:
# = Action View Base
#
- # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERB
- # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then Jim Weirich's Builder::XmlMarkup library is used.
+ # Action View templates can be written in several ways.
+ # If the template file has a <tt>.erb</tt> extension, then it uses the erubis[https://rubygems.org/gems/erubis]
+ # template system which can embed Ruby into an HTML document.
+ # If the template file has a <tt>.builder</tt> extension, then Jim Weirich's Builder::XmlMarkup library is used.
#
# == ERB
#
@@ -31,7 +33,9 @@ module ActionView #:nodoc:
#
# If you absolutely must write from within a function use +concat+.
#
- # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>.
+ # When on a line that only contains whitespaces except for the tag, <% %> suppress leading and trailing whitespace,
+ # including the trailing newline. <% %> and <%- -%> are the same.
+ # Note however that <%= %> and <%= -%> are different: only the latter removes trailing whitespaces.
#
# === Using sub templates
#
@@ -66,15 +70,6 @@ module ActionView #:nodoc:
# Headline: <%= headline %>
# First name: <%= person.first_name %>
#
- # If you need to find out whether a certain local variable has been assigned a value in a particular render call,
- # you need to use the following pattern:
- #
- # <% if local_assigns.has_key? :headline %>
- # Headline: <%= headline %>
- # <% end %>
- #
- # Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction.
- #
# === Template caching
#
# By default, Rails will compile each template to a method in order to render it. When you alter a template,
@@ -131,8 +126,8 @@ module ActionView #:nodoc:
# end
# end
#
- # For more information on Builder please consult the [source
- # code](https://github.com/jimweirich/builder).
+ # For more information on Builder please consult the {source
+ # code}[https://github.com/jimweirich/builder].
class Base
include Helpers, ::ERB::Util, Context
diff --git a/actionview/lib/action_view/buffers.rb b/actionview/lib/action_view/buffers.rb
index 361a0dccbe..be5d86b1dc 100644
--- a/actionview/lib/action_view/buffers.rb
+++ b/actionview/lib/action_view/buffers.rb
@@ -13,10 +13,11 @@ module ActionView
end
alias :append= :<<
- def safe_concat(value)
- return self if value.nil?
- super(value.to_s)
+ def safe_expr_append=(val)
+ return self if val.nil?
+ safe_concat val.to_s
end
+
alias :safe_append= :safe_concat
end
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index 0ccf2515c5..e34bdd4a46 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -53,6 +53,12 @@ module ActionView
\s* # followed by optional spaces
/x
+ # Part of any hash containing the :layout key
+ LAYOUT_HASH_KEY = /
+ (?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
+ \s* # followed by optional spaces
+ /x
+
# Matches:
# partial: "comments/comment", collection: @all_comments => "comments/comment"
# (object: @single_comment, partial: "comments/comment") => "comments/comment"
@@ -65,9 +71,9 @@ module ActionView
# topics => "topics/topic"
# (message.topics) => "topics/topic"
RENDER_ARGUMENTS = /\A
- (?:\s*\(?\s*) # optional opening paren surrounded by spaces
- (?:.*?#{PARTIAL_HASH_KEY})? # optional hash, up to the partial key declaration
- (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
+ (?:\s*\(?\s*) # optional opening paren surrounded by spaces
+ (?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
+ (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
/xm
def self.call(name, template)
@@ -85,8 +91,8 @@ module ActionView
attr_reader :name, :template
private :name, :template
- private
+ private
def source
template.source
end
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 72d79735ae..1f103786cb 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -60,7 +60,7 @@ module ActionView
def digest
Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
- logger.try :info, " Cache digest for #{template.inspect}: #{digest}"
+ logger.try :debug, " Cache digest for #{template.inspect}: #{digest}"
end
rescue ActionView::MissingTemplate
logger.try :error, " Couldn't find template for digesting: #{name}"
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 9266e55c47..4f45f5b8c8 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -1,12 +1,12 @@
module ActionView
- # Returns the version of the currently loaded ActionView as a <tt>Gem::Version</tt>
+ # Returns the version of the currently loaded Action View as a <tt>Gem::Version</tt>
def self.gem_version
Gem::Version.new VERSION::STRING
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 7333ea999a..b7fdc16a9d 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -7,7 +7,7 @@ module ActionView
# = Action View Asset Tag Helpers
module Helpers #:nodoc:
# This module provides methods for generating HTML that links views to assets such
- # as images, javascripts, stylesheets, and feeds. These methods do not verify
+ # as images, JavaScripts, stylesheets, and feeds. These methods do not verify
# the assets exist before linking to them:
#
# image_tag("rails.png")
@@ -218,7 +218,7 @@ module ActionView
tag("img", options)
end
- # Returns a string suitable for an html image tag alt attribute.
+ # Returns a string suitable for an HTML image tag alt attribute.
# The +src+ argument is meant to be an image file path.
# The method removes the basename of the file path and the digest,
# if any. It also removes hyphens and underscores from file names and
@@ -239,7 +239,7 @@ module ActionView
File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize
end
- # Returns an html video tag for the +sources+. If +sources+ is a string,
+ # Returns an HTML video tag for the +sources+. If +sources+ is a string,
# a single video tag will be returned. If +sources+ is an array, a video
# tag with nested source tags for each source will be returned. The
# +sources+ can be full paths or files that exists in your public videos
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 006b15be91..29733442c1 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -7,10 +7,10 @@ module ActionView
# urls.
#
# image_path("rails.png")
- # # => "/images/rails.png"
+ # # => "/assets/rails.png"
#
# image_url("rails.png")
- # # => "http://www.example.com/images/rails.png"
+ # # => "http://www.example.com/assets/rails.png"
#
# === Using asset hosts
#
@@ -121,8 +121,8 @@ module ActionView
# asset_path "application", type: :stylesheet # => /assets/application.css
# asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def asset_path(source, options = {})
- return "" unless source.present?
source = source.to_s
+ return "" unless source.present?
return source if source =~ URI_REGEXP
tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '')
@@ -155,7 +155,7 @@ module ActionView
#
# All other options provided are forwarded to +asset_path+ call.
#
- # asset_url "application.js" # => http://example.com/application.js
+ # asset_url "application.js" # => http://example.com/assets/application.js
# asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js
#
def asset_url(source, options = {})
@@ -203,7 +203,6 @@ module ActionView
request = self.request if respond_to?(:request)
host = options[:host]
host ||= config.asset_host if defined? config.asset_host
- host ||= request.base_url if request && options[:protocol] == :request
if host.respond_to?(:call)
arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
@@ -214,6 +213,7 @@ module ActionView
host = host % (Zlib.crc32(source) % 4)
end
+ host ||= request.base_url if request && options[:protocol] == :request
return unless host
if host =~ URI_REGEXP
@@ -231,13 +231,13 @@ module ActionView
end
end
- # Computes the path to a javascript asset in the public javascripts directory.
+ # Computes the path to a JavaScript asset in the public javascripts directory.
# If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
# Full paths from the document root will be passed through.
# Used internally by +javascript_include_tag+ to build the script path.
#
- # javascript_path "xmlhr" # => /javascripts/xmlhr.js
- # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
+ # javascript_path "xmlhr" # => /assets/xmlhr.js
+ # javascript_path "dir/xmlhr.js" # => /assets/dir/xmlhr.js
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
@@ -246,7 +246,7 @@ module ActionView
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
- # Computes the full URL to a javascript asset in the public javascripts directory.
+ # Computes the full URL to a JavaScript asset in the public javascripts directory.
# This will use +javascript_path+ internally, so most of their behaviors will be the same.
def javascript_url(source, options = {})
url_to_asset(source, {type: :javascript}.merge!(options))
@@ -258,8 +258,8 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
#
- # stylesheet_path "style" # => /stylesheets/style.css
- # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
+ # stylesheet_path "style" # => /assets/style.css
+ # stylesheet_path "dir/style.css" # => /assets/dir/style.css
# stylesheet_path "/dir/style.css" # => /dir/style.css
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
@@ -279,9 +279,9 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +image_tag+ to build the image path:
#
- # image_path("edit") # => "/images/edit"
- # image_path("edit.png") # => "/images/edit.png"
- # image_path("icons/edit.png") # => "/images/icons/edit.png"
+ # image_path("edit") # => "/assets/edit"
+ # image_path("edit.png") # => "/assets/edit.png"
+ # image_path("icons/edit.png") # => "/assets/icons/edit.png"
# image_path("/icons/edit.png") # => "/icons/edit.png"
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
#
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index 75d1634b2e..5a3223968f 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -31,7 +31,8 @@ module ActionView
# <head><title><%= @greeting %></title></head>
# <body>
# <b><%= @greeting %></b>
- # </body></html>
+ # </body>
+ # </html>
#
def capture(*args)
value = nil
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 2efb9612ac..4b4f0ae577 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -153,8 +153,8 @@ module ActionView
#
# Note that you cannot pass a <tt>Numeric</tt> value to <tt>time_ago_in_words</tt>.
#
- def time_ago_in_words(from_time, include_seconds_or_options = {})
- distance_of_time_in_words(from_time, Time.now, include_seconds_or_options)
+ def time_ago_in_words(from_time, options = {})
+ distance_of_time_in_words(from_time, Time.now, options)
end
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
@@ -177,7 +177,7 @@ module ActionView
# and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
# See <tt>Kernel.sprintf</tt> for documentation on format sequences.
# * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
- # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt>if
+ # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if
# you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
# the current selected year minus 5.
# * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
@@ -330,7 +330,7 @@ module ActionView
Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render
end
- # Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
+ # Returns a set of HTML select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
# +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with
# an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not
# supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add
@@ -379,7 +379,7 @@ module ActionView
DateTimeSelector.new(datetime, options, html_options).select_datetime
end
- # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
+ # Returns a set of HTML select-tags (one for year, month, and day) pre-selected with the +date+.
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order.
# If the array passed to the <tt>:order</tt> option does not contain all the three symbols, all tags will be hidden.
@@ -418,7 +418,7 @@ module ActionView
DateTimeSelector.new(date, options, html_options).select_date
end
- # Returns a set of html select-tags (one for hour and minute).
+ # Returns a set of HTML select-tags (one for hour and minute).
# You can set <tt>:time_separator</tt> key to format the output, and
# the <tt>:include_seconds</tt> option to include an input for seconds.
#
@@ -635,7 +635,7 @@ module ActionView
DateTimeSelector.new(date, options, html_options).select_year
end
- # Returns an html time tag for the given date or time.
+ # Returns an HTML time tag for the given date or time.
#
# time_tag Date.today # =>
# <time datetime="2010-11-04">November 04, 2010</time>
@@ -898,7 +898,7 @@ module ActionView
def translated_date_order
date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
- date_order = date_order.map { |element| element.to_sym }
+ date_order = date_order.map(&:to_sym)
forbidden_elements = date_order - [:year, :month, :day]
if forbidden_elements.any?
@@ -914,7 +914,7 @@ module ActionView
build_select(type, build_options(selected, options))
end
- # Build select option html from date value and options.
+ # Build select option HTML from date value and options.
# build_options(15, start: 1, end: 31)
# => "<option value="1">1</option>
# <option value="2">2</option>
@@ -954,7 +954,7 @@ module ActionView
(select_options.join("\n") + "\n").html_safe
end
- # Builds select tag from date type and html select options.
+ # Builds select tag from date type and HTML select options.
# build_select(:month, "<option value="1">January</option>...")
# => "<select id="post_written_on_2i" name="post[written_on(2i)]">
# <option value="1">January</option>...
@@ -1035,7 +1035,7 @@ module ActionView
def build_selects_from_types(order)
select = ''
first_visible = order.find { |type| !@options[:"discard_#{type}"] }
- order.reverse.each do |type|
+ order.reverse_each do |type|
separator = separator(type) unless type == first_visible # don't add before first visible field
select.insert(0, separator.to_s + send("select_#{type}").to_s)
end
diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb
index 16cddec339..ba47eee9ba 100644
--- a/actionview/lib/action_view/helpers/debug_helper.rb
+++ b/actionview/lib/action_view/helpers/debug_helper.rb
@@ -16,15 +16,15 @@ module ActionView
# # =>
# <pre class='debug_dump'>--- !ruby/object:User
# attributes:
- # &nbsp; updated_at:
- # &nbsp; username: testing
- # &nbsp; age: 42
- # &nbsp; password: xyz
- # &nbsp; created_at:
+ # updated_at:
+ # username: testing
+ # age: 42
+ # password: xyz
+ # created_at:
# </pre>
def debug(object)
Marshal::dump(object)
- object = ERB::Util.html_escape(object.to_yaml).gsub(" ", "&nbsp; ").html_safe
+ object = ERB::Util.html_escape(object.to_yaml)
content_tag(:pre, object, :class => "debug_dump")
rescue Exception # errors from Marshal or YAML
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 789a413c8d..c4371dc705 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -51,9 +51,7 @@ module ActionView
# The HTML generated for this would be (modulus formatting):
#
# <form action="/people" class="new_person" id="new_person" method="post">
- # <div style="display:none">
- # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
- # </div>
+ # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# <label for="person_first_name">First name</label>:
# <input id="person_first_name" name="person[first_name]" type="text" /><br />
#
@@ -81,10 +79,8 @@ module ActionView
# the code above as is would yield instead:
#
# <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
- # <div style="display:none">
- # <input name="_method" type="hidden" value="patch" />
- # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
- # </div>
+ # <input name="_method" type="hidden" value="patch" />
+ # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# <label for="person_first_name">First name</label>:
# <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br />
#
@@ -142,7 +138,7 @@ module ActionView
# will get expanded to
#
# <%= text_field :person, :first_name %>
- # which results in an html <tt><input></tt> tag whose +name+ attribute is
+ # which results in an HTML <tt><input></tt> tag whose +name+ attribute is
# <tt>person[first_name]</tt>. This means that when the form is submitted,
# the value entered by the user will be available in the controller as
# <tt>params[:person][:first_name]</tt>.
@@ -168,6 +164,23 @@ module ActionView
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
# id attributes on form elements. The namespace attribute will be prefixed
# with underscore on the generated HTML id.
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
+ # either "get" or "post". If "patch", "put", "delete", or another verb
+ # is used, a hidden input with name <tt>_method</tt> is added to
+ # simulate the verb over post.
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
+ # Use only if you need to pass custom authenticity token string, or to
+ # not add authenticity_token field at all (by passing <tt>false</tt>).
+ # Remote forms may omit the embedded authenticity token by setting
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
+ # This is helpful when you're fragment-caching the form. Remote forms
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
+ # unnecessary unless you support browsers without JavaScript.
+ # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive
+ # JavaScript drivers to control the submit behavior. By default this
+ # behavior is an ajax submit.
+ # * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name
+ # utf8 is not output.
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
#
# Also note that +form_for+ doesn't create an exclusive scope. It's still
@@ -315,9 +328,7 @@ module ActionView
# The HTML generated for this would be:
#
# <form action='http://www.example.com' method='post' data-remote='true'>
- # <div style='display:none'>
- # <input name='_method' type='hidden' value='patch' />
- # </div>
+ # <input name='_method' type='hidden' value='patch' />
# ...
# </form>
#
@@ -333,9 +344,7 @@ module ActionView
# The HTML generated for this would be:
#
# <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'>
- # <div style='display:none'>
- # <input name='_method' type='hidden' value='patch' />
- # </div>
+ # <input name='_method' type='hidden' value='patch' />
# ...
# </form>
#
@@ -428,6 +437,7 @@ module ActionView
html_options[:data] = options.delete(:data) if options.has_key?(:data)
html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
html_options[:method] = options.delete(:method) if options.has_key?(:method)
+ html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8)
html_options[:authenticity_token] = options.delete(:authenticity_token)
builder = instantiate_builder(object_name, object, options)
@@ -482,7 +492,7 @@ module ActionView
# Admin? : <%= permission_fields.check_box :admin %>
# <% end %>
#
- # <%= f.submit %>
+ # <%= person_form.submit %>
# <% end %>
#
# In this case, the checkbox field will be represented by an HTML +input+
@@ -1013,6 +1023,18 @@ module ActionView
# date_field("user", "born_on", value: "1984-05-12")
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
#
+ # You can create values for the "min" and "max" attributes by passing
+ # instances of Date or Time to the options hash.
+ #
+ # date_field("user", "born_on", min: Date.today)
+ # # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
+ #
+ # Alternatively, you can pass a String formatted as an ISO8601 date as the
+ # values for "min" and "max."
+ #
+ # date_field("user", "born_on", min: "2014-05-20")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
+ #
def date_field(object_name, method, options = {})
Tags::DateField.new(object_name, method, self, options).render
end
@@ -1030,6 +1052,18 @@ module ActionView
# time_field("task", "started_at")
# # => <input id="task_started_at" name="task[started_at]" type="time" />
#
+ # You can create values for the "min" and "max" attributes by passing
+ # instances of Date or Time to the options hash.
+ #
+ # time_field("task", "started_at", min: Time.now)
+ # # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
+ #
+ # Alternatively, you can pass a String formatted as an ISO8601 time as the
+ # values for "min" and "max."
+ #
+ # time_field("task", "started_at", min: "01:00:00")
+ # # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
+ #
def time_field(object_name, method, options = {})
Tags::TimeField.new(object_name, method, self, options).render
end
@@ -1047,6 +1081,18 @@ module ActionView
# datetime_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
#
+ # You can create values for the "min" and "max" attributes by passing
+ # instances of Date or Time to the options hash.
+ #
+ # datetime_field("user", "born_on", min: Date.today)
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
+ #
+ # Alternatively, you can pass a String formatted as an ISO8601 datetime
+ # with UTC offset as the values for "min" and "max."
+ #
+ # datetime_field("user", "born_on", min: "2014-05-20T00:00:00+0000")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
+ #
def datetime_field(object_name, method, options = {})
Tags::DatetimeField.new(object_name, method, self, options).render
end
@@ -1064,6 +1110,18 @@ module ActionView
# datetime_local_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
#
+ # You can create values for the "min" and "max" attributes by passing
+ # instances of Date or Time to the options hash.
+ #
+ # datetime_local_field("user", "born_on", min: Date.today)
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
+ #
+ # Alternatively, you can pass a String formatted as an ISO8601 datetime as
+ # the values for "min" and "max."
+ #
+ # datetime_local_field("user", "born_on", min: "2014-05-20T00:00:00")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
+ #
def datetime_local_field(object_name, method, options = {})
Tags::DatetimeLocalField.new(object_name, method, self, options).render
end
@@ -1191,8 +1249,8 @@ module ActionView
# end
#
# The above code creates a new method +div_radio_button+ which wraps a div
- # around the a new radio button. Note that when options are passed in, you
- # must called +objectify_options+ in order for the model object to get
+ # around the new radio button. Note that when options are passed in, you
+ # must call +objectify_options+ in order for the model object to get
# correctly passed to the method. If +objectify_options+ is not called,
# then the newly created helper will not be linked back to the model.
#
@@ -1815,8 +1873,8 @@ module ActionView
object = convert_to_model(@object)
key = object ? (object.persisted? ? :update : :create) : :submit
- model = if object.class.respond_to?(:model_name)
- object.class.model_name.human
+ model = if object.respond_to?(:model_name)
+ object.model_name.human
else
@object_name.to_s.humanize
end
@@ -1877,6 +1935,8 @@ module ActionView
end
ActiveSupport.on_load(:action_view) do
- cattr_accessor(:default_form_builder) { ::ActionView::Helpers::FormBuilder }
+ cattr_accessor(:default_form_builder, instance_writer: false, instance_reader: false) do
+ ::ActionView::Helpers::FormBuilder
+ end
end
end
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 48f42947db..bbfbf482a4 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -14,81 +14,81 @@ module ActionView
#
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
#
- # select("post", "category", Post::CATEGORIES, {include_blank: true})
+ # select("post", "category", Post::CATEGORIES, {include_blank: true})
#
- # could become:
+ # could become:
#
- # <select name="post[category]">
- # <option></option>
- # <option>joke</option>
- # <option>poem</option>
- # </select>
+ # <select name="post[category]">
+ # <option></option>
+ # <option>joke</option>
+ # <option>poem</option>
+ # </select>
#
- # Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
+ # Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
#
- # Example with @post.person_id => 2:
+ # Example with <tt>@post.person_id => 2</tt>:
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
#
- # could become:
+ # could become:
#
- # <select name="post[person_id]">
- # <option value="">None</option>
- # <option value="1">David</option>
- # <option value="2" selected="selected">Sam</option>
- # <option value="3">Tobias</option>
- # </select>
+ # <select name="post[person_id]">
+ # <option value="">None</option>
+ # <option value="1">David</option>
+ # <option value="2" selected="selected">Sam</option>
+ # <option value="3">Tobias</option>
+ # </select>
#
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
#
- # could become:
+ # could become:
#
- # <select name="post[person_id]">
- # <option value="">Select Person</option>
- # <option value="1">David</option>
- # <option value="2">Sam</option>
- # <option value="3">Tobias</option>
- # </select>
+ # <select name="post[person_id]">
+ # <option value="">Select Person</option>
+ # <option value="1">David</option>
+ # <option value="2">Sam</option>
+ # <option value="3">Tobias</option>
+ # </select>
#
- # Like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
- # option to be in the +html_options+ parameter.
+ # * <tt>:index</tt> - like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
+ # option to be in the +html_options+ parameter.
#
- # select("album[]", "genre", %w[rap rock country], {}, { index: nil })
+ # select("album[]", "genre", %w[rap rock country], {}, { index: nil })
#
- # becomes:
+ # becomes:
#
- # <select name="album[][genre]" id="album__genre">
- # <option value="rap">rap</option>
- # <option value="rock">rock</option>
- # <option value="country">country</option>
- # </select>
+ # <select name="album[][genre]" id="album__genre">
+ # <option value="rap">rap</option>
+ # <option value="rock">rock</option>
+ # <option value="country">country</option>
+ # </select>
#
# * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
#
- # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
+ # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
#
- # could become:
+ # could become:
#
- # <select name="post[category]">
- # <option></option>
- # <option>joke</option>
- # <option>poem</option>
- # <option disabled="disabled">restricted</option>
- # </select>
+ # <select name="post[category]">
+ # <option></option>
+ # <option>joke</option>
+ # <option>poem</option>
+ # <option disabled="disabled">restricted</option>
+ # </select>
#
- # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
+ # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
#
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
+ # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
#
- # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
- # <select name="post[category_id]">
- # <option value="1" disabled="disabled">2008 stuff</option>
- # <option value="2" disabled="disabled">Christmas</option>
- # <option value="3">Jokes</option>
- # <option value="4">Poems</option>
- # </select>
+ # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
+ # <select name="post[category_id]">
+ # <option value="1" disabled="disabled">2008 stuff</option>
+ # <option value="2" disabled="disabled">Christmas</option>
+ # <option value="3">Jokes</option>
+ # <option value="4">Poems</option>
+ # </select>
#
module FormOptionsHelper
# ERB::Util can mask some helpers like textilize. Make sure to include them.
@@ -152,11 +152,9 @@ module ActionView
# To prevent this the helper generates an auxiliary hidden field before
# every multiple select. The hidden field has the same name as multiple select and blank value.
#
- # This way, the client either sends only the hidden field (representing
- # the deselected multiple select box), or both fields. Since the HTML specification
- # says key/value pairs have to be sent in the same order they appear in the
- # form, and parameters extraction gets the last occurrence of any repeated
- # key in the query string, that works for ordinary forms.
+ # <b>Note:</b> The client either sends only the hidden field (representing
+ # the deselected multiple select box), or both fields. This means that the resulting array
+ # always contains a blank string.
#
# In case if you don't want the helper to generate this hidden field you can specify
# <tt>include_hidden: false</tt> option.
@@ -316,7 +314,7 @@ module ActionView
# # => <option>MasterCard</option>
# # => <option selected="selected">Discover</option>
#
- # You can optionally provide html attributes as the last element of the array.
+ # You can optionally provide HTML attributes as the last element of the array.
#
# options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"])
# # => <option value="Denmark">Denmark</option>
@@ -353,12 +351,12 @@ module ActionView
return container if String === container
selected, disabled = extract_selected_and_disabled(selected).map do |r|
- Array(r).map { |item| item.to_s }
+ Array(r).map(&:to_s)
end
container.map do |element|
html_attributes = option_html_attributes(element)
- text, value = option_text_and_value(element).map { |item| item.to_s }
+ text, value = option_text_and_value(element).map(&:to_s)
html_attributes[:selected] ||= option_value_selected?(value, selected)
html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
@@ -463,21 +461,7 @@ module ActionView
end
# Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
- # wraps them with <tt><optgroup></tt> tags.
- #
- # Parameters:
- # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
- # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
- # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
- # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
- # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
- # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
- # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
- #
- # Options:
- # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
- # prepends an option with a generic prompt - "Please select" - or the given prompt string.
- # * <tt>:divider</tt> - the divider for the options groups.
+ # wraps them with <tt><optgroup></tt> tags:
#
# grouped_options = [
# ['North America',
@@ -504,22 +488,36 @@ module ActionView
# <option value="France">France</option>
# </optgroup>
#
- # grouped_options = [
- # [['United States','US'], 'Canada'],
- # ['Denmark','Germany','France']
- # ]
- # grouped_options_for_select(grouped_options, nil, divider: '---------')
+ # Parameters:
+ # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
+ # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
+ # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
+ # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
+ # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
+ # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
+ # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
#
- # Possible output:
- # <optgroup label="---------">
- # <option value="US">United States</option>
- # <option value="Canada">Canada</option>
- # </optgroup>
- # <optgroup label="---------">
- # <option value="Denmark">Denmark</option>
- # <option value="Germany">Germany</option>
- # <option value="France">France</option>
- # </optgroup>
+ # Options:
+ # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
+ # prepends an option with a generic prompt - "Please select" - or the given prompt string.
+ # * <tt>:divider</tt> - the divider for the options groups.
+ #
+ # grouped_options = [
+ # [['United States','US'], 'Canada'],
+ # ['Denmark','Germany','France']
+ # ]
+ # grouped_options_for_select(grouped_options, nil, divider: '---------')
+ #
+ # Possible output:
+ # <optgroup label="---------">
+ # <option value="US">United States</option>
+ # <option value="Canada">Canada</option>
+ # </optgroup>
+ # <optgroup label="---------">
+ # <option value="Denmark">Denmark</option>
+ # <option value="Germany">Germany</option>
+ # <option value="France">France</option>
+ # </optgroup>
#
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
@@ -635,7 +633,7 @@ module ActionView
# even use the label as wrapper, as in the example above.
#
# The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept
- # extra html options:
+ # extra HTML options:
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
# b.label(class: "radio_button") { b.radio_button(class: "radio_button") }
# end
@@ -698,7 +696,7 @@ module ActionView
# use the label as wrapper, as in the example above.
#
# The builder methods <tt>label</tt> and <tt>check_box</tt> also accept
- # extra html options:
+ # extra HTML options:
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
# b.label(class: "check_box") { b.check_box(class: "check_box") }
# end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 9c0c43d096..93c04fbec6 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -35,10 +35,10 @@ module ActionView
# This is helpful when you're fragment-caching the form. Remote forms get the
# authenticity token from the <tt>meta</tt> tag, so embedding is unnecessary unless you
# support browsers without JavaScript.
- # * A list of parameters to feed to the URL the form will be posted to.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
# submit behavior. By default this behavior is an ajax submit.
# * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name utf8 is not output.
+ # * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
# form_tag('/posts')
@@ -133,12 +133,20 @@ module ActionView
option_tags ||= ""
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
- if options.delete(:include_blank)
- option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags)
+ if options.include?(:include_blank)
+ include_blank = options.delete(:include_blank)
+
+ if include_blank == true
+ include_blank = ''
+ end
+
+ if include_blank
+ option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags)
+ end
end
if prompt = options.delete(:prompt)
- option_tags = content_tag(:option, prompt, :value => '').safe_concat(option_tags)
+ option_tags = content_tag(:option, prompt, value: '').safe_concat(option_tags)
end
content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
@@ -224,7 +232,7 @@ module ActionView
# # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
# # type="hidden" value="" />
def hidden_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
+ text_field_tag(name, value, options.merge(type: :hidden))
end
# Creates a file upload field. If you are using file uploads then you will also need
@@ -263,7 +271,7 @@ module ActionView
# file_field_tag 'file', accept: 'text/html', class: 'upload', value: 'index.html'
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
def file_field_tag(name, options = {})
- text_field_tag(name, nil, options.update("type" => "file"))
+ text_field_tag(name, nil, options.merge(type: :file))
end
# Creates a password field, a masked text field that will hide the users input behind a mask character.
@@ -296,7 +304,7 @@ module ActionView
# password_field_tag 'pin', '1234', maxlength: 4, size: 6, class: "pin_input"
# # => <input class="pin_input" id="pin" maxlength="4" name="pin" size="6" type="password" value="1234" />
def password_field_tag(name = "password", value = nil, options = {})
- text_field_tag(name, value, options.update("type" => "password"))
+ text_field_tag(name, value, options.merge(type: :password))
end
# Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.
@@ -508,19 +516,19 @@ module ActionView
#
# ==== Examples
# image_submit_tag("login.png")
- # # => <input alt="Login" src="/images/login.png" type="image" />
+ # # => <input alt="Login" src="/assets/login.png" type="image" />
#
# image_submit_tag("purchase.png", disabled: true)
- # # => <input alt="Purchase" disabled="disabled" src="/images/purchase.png" type="image" />
+ # # => <input alt="Purchase" disabled="disabled" src="/assets/purchase.png" type="image" />
#
# image_submit_tag("search.png", class: 'search_button', alt: 'Find')
- # # => <input alt="Find" class="search_button" src="/images/search.png" type="image" />
+ # # => <input alt="Find" class="search_button" src="/assets/search.png" type="image" />
#
# image_submit_tag("agree.png", disabled: true, class: "agree_disagree_button")
- # # => <input alt="Agree" class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
+ # # => <input alt="Agree" class="agree_disagree_button" disabled="disabled" src="/assets/agree.png" type="image" />
#
# image_submit_tag("save.png", data: { confirm: "Are you sure?" })
- # # => <input alt="Save" src="/images/save.png" data-confirm="Are you sure?" type="image" />
+ # # => <input alt="Save" src="/assets/save.png" data-confirm="Are you sure?" type="image" />
def image_submit_tag(source, options = {})
options = options.stringify_keys
tag :input, { "alt" => image_alt(source), "type" => "image", "src" => path_to_image(source) }.update(options)
@@ -571,7 +579,7 @@ module ActionView
# color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="color" name="color" type="color" value="#DEF726" />
def color_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "color"))
+ text_field_tag(name, value, options.merge(type: :color))
end
# Creates a text field of type "search".
@@ -592,7 +600,7 @@ module ActionView
# search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="search" name="search" type="search" value="Enter your search query here" />
def search_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "search"))
+ text_field_tag(name, value, options.merge(type: :search))
end
# Creates a text field of type "tel".
@@ -613,7 +621,7 @@ module ActionView
# telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="tel" name="tel" type="tel" value="0123456789" />
def telephone_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "tel"))
+ text_field_tag(name, value, options.merge(type: :tel))
end
alias phone_field_tag telephone_field_tag
@@ -635,7 +643,7 @@ module ActionView
# date_field_tag 'date', '01/01/2014', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="date" name="date" type="date" value="01/01/2014" />
def date_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
+ text_field_tag(name, value, options.merge(type: :date))
end
# Creates a text field of type "time".
@@ -646,7 +654,7 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def time_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "time"))
+ text_field_tag(name, value, options.merge(type: :time))
end
# Creates a text field of type "datetime".
@@ -657,7 +665,7 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def datetime_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "datetime"))
+ text_field_tag(name, value, options.merge(type: :datetime))
end
# Creates a text field of type "datetime-local".
@@ -668,7 +676,7 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def datetime_local_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local"))
+ text_field_tag(name, value, options.merge(type: 'datetime-local'))
end
# Creates a text field of type "month".
@@ -679,7 +687,7 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def month_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "month"))
+ text_field_tag(name, value, options.merge(type: :month))
end
# Creates a text field of type "week".
@@ -690,7 +698,7 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def week_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "week"))
+ text_field_tag(name, value, options.merge(type: :week))
end
# Creates a text field of type "url".
@@ -711,7 +719,7 @@ module ActionView
# url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="url" name="url" type="url" value="http://rubyonrails.org" />
def url_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "url"))
+ text_field_tag(name, value, options.merge(type: :url))
end
# Creates a text field of type "email".
@@ -732,7 +740,7 @@ module ActionView
# email_field_tag 'email', 'email@example.com', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="email" name="email" type="email" value="email@example.com" />
def email_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "email"))
+ text_field_tag(name, value, options.merge(type: :email))
end
# Creates a number field.
@@ -790,7 +798,7 @@ module ActionView
# ==== Options
# * Accepts the same options as number_field_tag.
def range_field_tag(name, value = nil, options = {})
- number_field_tag(name, value, options.stringify_keys.update("type" => "range"))
+ number_field_tag(name, value, options.merge(type: :range))
end
# Creates the hidden UTF8 enforcer tag. Override this method in a helper
@@ -862,7 +870,7 @@ module ActionView
# see http://www.w3.org/TR/html4/types.html#type-name
def sanitize_to_id(name)
- name.to_s.delete(']').gsub(/[^-a-zA-Z0-9:.]/, "_")
+ name.to_s.delete(']').tr('^-a-zA-Z0-9:.', "_")
end
end
end
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 7220bded3c..f66dbfe7d3 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -306,12 +306,12 @@ module ActionView
# string containing an i18n scope where to find this hash. It
# might have the following keys:
# * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
- # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
- # *<tt>:billion</tt>, <tt>:trillion</tt>,
- # *<tt>:quadrillion</tt>
+ # <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
+ # <tt>:billion</tt>, <tt>:trillion</tt>,
+ # <tt>:quadrillion</tt>
# * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
- # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
- # *<tt>:pico</tt>, <tt>:femto</tt>
+ # <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
+ # <tt>:pico</tt>, <tt>:femto</tt>
# * <tt>:format</tt> - Sets the format of the output string
# (defaults to "%n %u"). The field types are:
# * %u - The quantifier (ex.: 'thousand')
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index b0d9c7c7f9..1c2a400245 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -17,10 +17,10 @@ module ActionView #:nodoc:
stringish.to_s.html_safe
end
- # This method returns a html safe string similar to what <tt>Array#join</tt>
+ # This method returns an HTML safe string similar to what <tt>Array#join</tt>
# would return. The array is flattened, and all items, including
- # the supplied separator, are html escaped unless they are html
- # safe, and the returned string is marked as html safe.
+ # the supplied separator, are HTML escaped unless they are HTML
+ # safe, and the returned string is marked as HTML safe.
#
# safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
# # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index ebfc35a4c7..e11670e00d 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -13,13 +13,13 @@ module ActionView
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
# * <tt>:plain</tt> - Renders the text passed in out. Setting the content
- # type as <tt>text/plain</tt>.
- # * <tt>:html</tt> - Renders the html safe string passed in out, otherwise
- # performs html escape on the string first. Setting the content type as
- # <tt>text/html</tt>.
+ # type as <tt>text/plain</tt>.
+ # * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
+ # performs HTML escape on the string first. Setting the content type as
+ # <tt>text/html</tt>.
# * <tt>:body</tt> - Renders the text passed in, and inherits the content
- # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
- # object.
+ # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
+ # object.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 049af275b6..7cb55cc214 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/object/try'
-require 'action_view/vendor/html-scanner'
+require 'active_support/deprecation'
+require 'rails-html-sanitizer'
module ActionView
# = Action View Sanitize Helpers
@@ -8,7 +9,7 @@ module ActionView
# These helper methods extend Action View making them callable within your template files.
module SanitizeHelper
extend ActiveSupport::Concern
- # This +sanitize+ helper will html encode all tags and strip all attributes that
+ # This +sanitize+ helper will HTML encode all tags and strip all attributes that
# aren't specifically allowed.
#
# It also strips href/src tags with invalid protocols, like javascript: especially.
@@ -27,14 +28,36 @@ module ActionView
#
# <%= sanitize @article.body %>
#
- # Custom Use (only the mentioned tags and attributes are allowed, nothing else)
+ # Custom Use - Custom Scrubber
+ # (supply a Loofah::Scrubber that does the sanitization)
+ #
+ # scrubber can either wrap a block:
+ # scrubber = Loofah::Scrubber.new do |node|
+ # node.text = "dawn of cats"
+ # end
+ #
+ # or be a subclass of Loofah::Scrubber which responds to scrub:
+ # class KittyApocalypse < Loofah::Scrubber
+ # def scrub(node)
+ # node.text = "dawn of cats"
+ # end
+ # end
+ # scrubber = KittyApocalypse.new
+ #
+ # <%= sanitize @article.body, scrubber: scrubber %>
+ #
+ # A custom scrubber takes precedence over custom tags and attributes
+ # Learn more about scrubbers here: https://github.com/flavorjones/loofah
+ #
+ # Custom Use - tags and attributes
+ # (only the mentioned tags and attributes are allowed, nothing else)
#
# <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %>
#
# Add table tags to the default allowed tags
#
# class Application < Rails::Application
- # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
+ # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
# end
#
# Remove tags to the default allowed tags
@@ -65,9 +88,9 @@ module ActionView
self.class.white_list_sanitizer.sanitize_css(style)
end
- # Strips all HTML tags from the +html+, including comments. This uses the
- # html-scanner tokenizer and so its HTML parsing ability is limited by
- # that of html-scanner.
+ # Strips all HTML tags from the +html+, including comments. This uses
+ # Nokogiri for tokenization (via Loofah) and so its HTML parsing ability
+ # is limited by that of Nokogiri.
#
# strip_tags("Strip <i>these</i> tags!")
# # => Strip these tags!
@@ -98,47 +121,21 @@ module ActionView
module ClassMethods #:nodoc:
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
- def sanitized_protocol_separator
- white_list_sanitizer.protocol_separator
- end
-
- def sanitized_uri_attributes
- white_list_sanitizer.uri_attributes
- end
-
- def sanitized_bad_tags
- white_list_sanitizer.bad_tags
+ # Vendors the full, link and white list sanitizers.
+ # Provided strictly for compabitility and can be removed in Rails 5.
+ def sanitizer_vendor
+ Rails::Html::Sanitizer
end
def sanitized_allowed_tags
- white_list_sanitizer.allowed_tags
+ sanitizer_vendor.white_list_sanitizer.allowed_tags
end
def sanitized_allowed_attributes
- white_list_sanitizer.allowed_attributes
- end
-
- def sanitized_allowed_css_properties
- white_list_sanitizer.allowed_css_properties
- end
-
- def sanitized_allowed_css_keywords
- white_list_sanitizer.allowed_css_keywords
+ sanitizer_vendor.white_list_sanitizer.allowed_attributes
end
- def sanitized_shorthand_css_properties
- white_list_sanitizer.shorthand_css_properties
- end
-
- def sanitized_allowed_protocols
- white_list_sanitizer.allowed_protocols
- end
-
- def sanitized_protocol_separator=(value)
- white_list_sanitizer.protocol_separator = value
- end
-
- # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
+ # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
# any object that responds to +sanitize+.
#
# class Application < Rails::Application
@@ -146,21 +143,21 @@ module ActionView
# end
#
def full_sanitizer
- @full_sanitizer ||= HTML::FullSanitizer.new
+ @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
end
- # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
- # any object that responds to +sanitize+.
+ # Gets the Rails::Html::LinkSanitizer instance used by +strip_links+.
+ # Replace with any object that responds to +sanitize+.
#
# class Application < Rails::Application
# config.action_view.link_sanitizer = MySpecialSanitizer.new
# end
#
def link_sanitizer
- @link_sanitizer ||= HTML::LinkSanitizer.new
+ @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
end
- # Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
+ # Gets the Rails::Html::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
# Replace with any object that responds to +sanitize+.
#
# class Application < Rails::Application
@@ -168,88 +165,32 @@ module ActionView
# end
#
def white_list_sanitizer
- @white_list_sanitizer ||= HTML::WhiteListSanitizer.new
+ @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
end
- # Adds valid HTML attributes that the +sanitize+ helper checks for URIs.
+ ##
+ # :method: sanitized_allowed_tags=
#
- # class Application < Rails::Application
- # config.action_view.sanitized_uri_attributes = 'lowsrc', 'target'
- # end
+ # :call-seq: sanitized_allowed_tags=(tags)
#
- def sanitized_uri_attributes=(attributes)
- HTML::WhiteListSanitizer.uri_attributes.merge(attributes)
- end
-
- # Adds to the Set of 'bad' tags for the +sanitize+ helper.
+ # Replaces the allowed tags for the +sanitize+ helper.
#
# class Application < Rails::Application
- # config.action_view.sanitized_bad_tags = 'embed', 'object'
+ # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
# end
#
- def sanitized_bad_tags=(attributes)
- HTML::WhiteListSanitizer.bad_tags.merge(attributes)
- end
- # Adds to the Set of allowed tags for the +sanitize+ helper.
+ ##
+ # :method: sanitized_allowed_attributes=
#
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
- # end
+ # :call-seq: sanitized_allowed_attributes=(attributes)
#
- def sanitized_allowed_tags=(attributes)
- HTML::WhiteListSanitizer.allowed_tags.merge(attributes)
- end
-
- # Adds to the Set of allowed HTML attributes for the +sanitize+ helper.
+ # Replaces the allowed HTML attributes for the +sanitize+ helper.
#
# class Application < Rails::Application
# config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc']
# end
#
- def sanitized_allowed_attributes=(attributes)
- HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
- end
-
- # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_css_properties = 'expression'
- # end
- #
- def sanitized_allowed_css_properties=(attributes)
- HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes)
- end
-
- # Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_css_keywords = 'expression'
- # end
- #
- def sanitized_allowed_css_keywords=(attributes)
- HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes)
- end
-
- # Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_shorthand_css_properties = 'expression'
- # end
- #
- def sanitized_shorthand_css_properties=(attributes)
- HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes)
- end
-
- # Adds to the Set of allowed protocols for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_protocols = 'ssh', 'feed'
- # end
- #
- def sanitized_allowed_protocols=(attributes)
- HTML::WhiteListSanitizer.allowed_protocols.merge(attributes)
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index 35444bcfb4..a87c223a71 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -9,6 +9,7 @@ module ActionView
module TagHelper
extend ActiveSupport::Concern
include CaptureHelper
+ include OutputSafetyHelper
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
autoplay controls loop selected hidden scoped async
@@ -17,7 +18,9 @@ module ActionView
itemscope allowfullscreen default inert sortable
truespeed typemustmatch).to_set
- BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
+ BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
+
+ TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set
PRE_CONTENT_STRINGS = {
:textarea => "\n"
@@ -120,7 +123,7 @@ module ActionView
# cdata_section("hello]]>world")
# # => <![CDATA[hello]]]]><![CDATA[>world]]>
def cdata_section(content)
- splitted = content.to_s.gsub(']]>', ']]]]><![CDATA[>')
+ splitted = content.to_s.gsub(/\]\]\>/, ']]]]><![CDATA[>')
"<![CDATA[#{splitted}]]>".html_safe
end
@@ -147,9 +150,9 @@ module ActionView
return if options.blank?
attrs = []
options.each_pair do |key, value|
- if key.to_s == 'data' && value.is_a?(Hash)
+ if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
value.each_pair do |k, v|
- attrs << data_tag_option(k, v, escape)
+ attrs << prefix_tag_option(key, k, v, escape)
end
elsif BOOLEAN_ATTRIBUTES.include?(key)
attrs << boolean_tag_option(key) if value
@@ -157,11 +160,11 @@ module ActionView
attrs << tag_option(key, value, escape)
end
end
- " #{attrs.sort! * ' '}" unless attrs.empty?
+ " #{attrs * ' '}" unless attrs.empty?
end
- def data_tag_option(key, value, escape)
- key = "data-#{key.to_s.dasherize}"
+ def prefix_tag_option(prefix, key, value, escape)
+ key = "#{prefix}-#{key.to_s.dasherize}"
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
value = value.to_json
end
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index 8607da301c..f8abb19698 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -25,7 +25,7 @@ module ActionView
private
def value(object)
- object.send @method_name if object
+ object.public_send @method_name if object
end
def value_before_type_cast(object)
diff --git a/actionview/lib/action_view/helpers/tags/datetime_field.rb b/actionview/lib/action_view/helpers/tags/datetime_field.rb
index 25e7e05ec6..b2cee9d198 100644
--- a/actionview/lib/action_view/helpers/tags/datetime_field.rb
+++ b/actionview/lib/action_view/helpers/tags/datetime_field.rb
@@ -5,8 +5,8 @@ module ActionView
def render
options = @options.stringify_keys
options["value"] ||= format_date(value(object))
- options["min"] = format_date(options["min"])
- options["max"] = format_date(options["max"])
+ options["min"] = format_date(datetime_value(options["min"]))
+ options["max"] = format_date(datetime_value(options["max"]))
@options = options
super
end
@@ -16,6 +16,14 @@ module ActionView
def format_date(value)
value.try(:strftime, "%Y-%m-%dT%T.%L%z")
end
+
+ def datetime_value(value)
+ if value.is_a? String
+ DateTime.parse(value) rescue nil
+ else
+ value
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index a5bcaf8153..08a23e497e 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -2,6 +2,39 @@ module ActionView
module Helpers
module Tags # :nodoc:
class Label < Base # :nodoc:
+ class LabelBuilder # :nodoc:
+ attr_reader :object
+
+ def initialize(template_object, object_name, method_name, object, tag_value)
+ @template_object = template_object
+ @object_name = object_name
+ @method_name = method_name
+ @object = object
+ @tag_value = tag_value
+ end
+
+ def translation
+ method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name
+ @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
+
+ if object.respond_to?(:to_model)
+ key = object.model_name.i18n_key
+ i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
+ end
+
+ i18n_default ||= ""
+ content = I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
+
+ content ||= if object && object.class.respond_to?(:human_attribute_name)
+ object.class.human_attribute_name(method_and_value)
+ end
+
+ content ||= @method_name.humanize
+
+ content
+ end
+ end
+
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
options ||= {}
@@ -32,33 +65,24 @@ module ActionView
options.delete("namespace")
options["for"] = name_and_id["id"] unless options.key?("for")
- if block_given?
- content = @template_object.capture(&block)
- else
- method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
- content = if @content.blank?
- @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
-
- if object.respond_to?(:to_model)
- key = object.class.model_name.i18n_key
- i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
- end
-
- i18n_default ||= ""
- I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
- else
- @content.to_s
- end
-
- content ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(method_and_value)
- end
+ builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
- content ||= @method_name.humanize
+ content = if block_given?
+ @template_object.capture(builder, &block)
+ elsif @content.present?
+ @content.to_s
+ else
+ render_component(builder)
end
label_tag(name_and_id["id"], content, options)
end
+
+ private
+
+ def render_component(builder)
+ builder.translation
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/placeholderable.rb b/actionview/lib/action_view/helpers/tags/placeholderable.rb
new file mode 100644
index 0000000000..ae67bc13af
--- /dev/null
+++ b/actionview/lib/action_view/helpers/tags/placeholderable.rb
@@ -0,0 +1,34 @@
+module ActionView
+ module Helpers
+ module Tags # :nodoc:
+ module Placeholderable # :nodoc:
+ def initialize(*)
+ super
+
+ if tag_value = @options[:placeholder]
+ placeholder = tag_value if tag_value.is_a?(String)
+
+ object_name = @object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
+ method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}"
+
+ if object.respond_to?(:to_model)
+ key = object.class.model_name.i18n_key
+ i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
+ end
+
+ i18n_default ||= ""
+ placeholder ||= I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.placeholder").presence
+
+ placeholder ||= if object && object.class.respond_to?(:human_attribute_name)
+ object.class.human_attribute_name(method_and_value)
+ end
+
+ placeholder ||= @method_name.humanize
+
+ @options[:placeholder] = placeholder
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/helpers/tags/search_field.rb b/actionview/lib/action_view/helpers/tags/search_field.rb
index c09e2f1be7..a848aeabfa 100644
--- a/actionview/lib/action_view/helpers/tags/search_field.rb
+++ b/actionview/lib/action_view/helpers/tags/search_field.rb
@@ -16,6 +16,7 @@ module ActionView
options["incremental"] = true unless options.has_key?("incremental")
end
+ @options = options
super
end
end
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index 00881d9978..180900cc8d 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -3,7 +3,7 @@ module ActionView
module Tags # :nodoc:
class Select < Base # :nodoc:
def initialize(object_name, method_name, template_object, choices, options, html_options)
- @choices = block_given? ? template_object.capture { yield } : choices
+ @choices = block_given? ? template_object.capture { yield || "" } : choices
@choices = @choices.to_a if @choices.is_a?(Range)
@html_options = html_options
diff --git a/actionview/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb
index 9ee83ee7c2..69038c1498 100644
--- a/actionview/lib/action_view/helpers/tags/text_area.rb
+++ b/actionview/lib/action_view/helpers/tags/text_area.rb
@@ -1,7 +1,11 @@
+require 'action_view/helpers/tags/placeholderable'
+
module ActionView
module Helpers
module Tags # :nodoc:
class TextArea < Base # :nodoc:
+ include Placeholderable
+
def render
options = @options.stringify_keys
add_default_name_and_id(options)
diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb
index e0b80d81c2..5c576a20ca 100644
--- a/actionview/lib/action_view/helpers/tags/text_field.rb
+++ b/actionview/lib/action_view/helpers/tags/text_field.rb
@@ -1,7 +1,11 @@
+require 'action_view/helpers/tags/placeholderable'
+
module ActionView
module Helpers
module Tags # :nodoc:
class TextField < Base # :nodoc:
+ include Placeholderable
+
def render
options = @options.stringify_keys
options["size"] = options["maxlength"] unless options.key?("size")
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 7cfbca5b6f..2c40ed1832 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -103,11 +103,14 @@ module ActionView
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
# as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
- # '<mark>\1</mark>')
+ # '<mark>\1</mark>') or passing a block that receives each matched term.
#
# highlight('You searched for: rails', 'rails')
# # => You searched for: <mark>rails</mark>
#
+ # highlight('You searched for: rails', /for|rails/)
+ # # => You searched <mark>for</mark>: <mark>rails</mark>
+ #
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
# # => You searched for: ruby, rails, dhh
#
@@ -116,15 +119,25 @@ module ActionView
#
# highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
# # => You searched for: <a href="search?q=rails">rails</a>
+ #
+ # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
+ # # => You searched for: <a href="search?q=rails">rails</a>
def highlight(text, phrases, options = {})
text = sanitize(text) if options.fetch(:sanitize, true)
if text.blank? || phrases.blank?
- text
+ text || ""
else
- highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
- match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
+ match = Array(phrases).map do |p|
+ Regexp === p ? p.to_s : Regexp.escape(p)
+ end.join('|')
+
+ if block_given?
+ text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found }
+ else
+ highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
+ text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
+ end
end.html_safe
end
@@ -155,9 +168,13 @@ module ActionView
def excerpt(text, phrase, options = {})
return unless text && phrase
- separator = options[:separator] || ''
- phrase = Regexp.escape(phrase)
- regex = /#{phrase}/i
+ separator = options.fetch(:separator, nil) || ""
+ case phrase
+ when Regexp
+ regex = phrase
+ else
+ regex = /#{Regexp.escape(phrase)}/i
+ end
return unless matches = text.match(regex)
phrase = matches[0]
@@ -171,7 +188,7 @@ module ActionView
end
end
- first_part, second_part = text.split(regex, 2)
+ first_part, second_part = text.split(phrase, 2)
prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
@@ -292,7 +309,7 @@ module ActionView
# <table>
# <% @items.each do |item| %>
# <tr class="<%= cycle("odd", "even") -%>">
- # <td>item</td>
+ # <td><%= item %></td>
# </tr>
# <% end %>
# </table>
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 17ec6a40bf..c2fda42396 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -1,10 +1,12 @@
require 'action_view/helpers/tag_helper'
+require 'active_support/core_ext/string/access'
require 'i18n/exceptions'
module ActionView
# = Action View Translation Helpers
module Helpers
module TranslationHelper
+ include TagHelper
# Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
#
# First, it will ensure that any thrown +MissingTranslation+ messages will be turned
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index c3be47133c..8c2d5705f1 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -229,59 +229,51 @@ module ActionView
# ==== Examples
# <%= button_to "New", action: "new" %>
# # => "<form method="post" action="/controller/new" class="button_to">
- # # <div><input value="New" type="submit" /></div>
+ # # <input value="New" type="submit" />
# # </form>"
#
# <%= button_to "New", new_articles_path %>
# # => "<form method="post" action="/articles/new" class="button_to">
- # # <div><input value="New" type="submit" /></div>
+ # # <input value="New" type="submit" />
# # </form>"
#
# <%= button_to [:make_happy, @user] do %>
# Make happy <strong><%= @user.name %></strong>
# <% end %>
# # => "<form method="post" action="/users/1/make_happy" class="button_to">
- # # <div>
- # # <button type="submit">
- # # Make happy <strong><%= @user.name %></strong>
- # # </button>
- # # </div>
+ # # <button type="submit">
+ # # Make happy <strong><%= @user.name %></strong>
+ # # </button>
# # </form>"
#
# <%= button_to "New", { action: "new" }, form_class: "new-thing" %>
# # => "<form method="post" action="/controller/new" class="new-thing">
- # # <div><input value="New" type="submit" /></div>
+ # # <input value="New" type="submit" />
# # </form>"
#
#
# <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %>
# # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
- # # <div>
- # # <input value="Create" type="submit" />
- # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
- # # </div>
+ # # <input value="Create" type="submit" />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </form>"
#
#
# <%= button_to "Delete Image", { action: "delete", id: @image.id },
# method: :delete, data: { confirm: "Are you sure?" } %>
# # => "<form method="post" action="/images/delete/1" class="button_to">
- # # <div>
- # # <input type="hidden" name="_method" value="delete" />
- # # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
- # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
- # # </div>
+ # # <input type="hidden" name="_method" value="delete" />
+ # # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </form>"
#
#
# <%= button_to('Destroy', 'http://www.example.com',
# method: "delete", remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %>
# # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
- # # <div>
- # # <input name='_method' value='delete' type='hidden' />
- # # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
- # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
- # # </div>
+ # # <input name='_method' value='delete' type='hidden' />
+ # # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </form>"
# #
def button_to(name = nil, options = nil, html_options = nil, &block)
@@ -436,6 +428,7 @@ module ActionView
# * <tt>:body</tt> - Preset the body of the email.
# * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
+ # * <tt>:reply_to</tt> - Preset the Reply-To field of the email.
#
# ==== Obfuscation
# Prior to Rails 4.0, +mail_to+ provided options for encoding the address
@@ -465,9 +458,9 @@ module ActionView
html_options, name = name, nil if block_given?
html_options = (html_options || {}).stringify_keys
- extras = %w{ cc bcc body subject }.map! { |item|
+ extras = %w{ cc bcc body subject reply_to }.map! { |item|
option = html_options.delete(item) || next
- "#{item}=#{Rack::Utils.escape_path(option)}"
+ "#{item.dasherize}=#{Rack::Utils.escape_path(option)}"
}.compact
extras = extras.empty? ? '' : '?' + extras.join('&')
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index 9ee05bd816..0b5c0b9991 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -262,7 +262,7 @@ module ActionView
def layout(layout, conditions = {})
include LayoutConditions unless conditions.empty?
- conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
+ conditions.each {|k, v| conditions[k] = Array(v).map(&:to_s) }
self._layout_conditions = conditions
self._layout = layout
diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb
index 6c8d9cb5bf..9047dbdd85 100644
--- a/actionview/lib/action_view/log_subscriber.rb
+++ b/actionview/lib/action_view/log_subscriber.rb
@@ -13,11 +13,11 @@ module ActionView
end
def render_template(event)
- return unless logger.info?
- message = " Rendered #{from_rails_root(event.payload[:identifier])}"
- message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
- message << " (#{event.duration.round(1)}ms)"
- info(message)
+ info do
+ message = " Rendered #{from_rails_root(event.payload[:identifier])}"
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
+ message << " (#{event.duration.round(1)}ms)"
+ end
end
alias :render_partial :render_template
alias :render_collection :render_template
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 5fff6b0771..36855ec3d0 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -66,10 +66,7 @@ module ActionView
def self.get(details)
if details[:formats]
details = details.dup
- syms = Set.new Mime::SET.symbols
- details[:formats] = details[:formats].select { |v|
- syms.include? v
- }
+ details[:formats] &= Mime::SET.symbols
end
@details_keys[details] ||= new
end
@@ -194,7 +191,6 @@ module ActionView
def initialize(view_paths, details = {}, prefixes = [])
@details, @details_key = {}, nil
- @skip_default_locale = false
@cache = true
@prefixes = prefixes
@rendered_format = nil
@@ -216,12 +212,6 @@ module ActionView
super(values)
end
- # Do not use the default locale on template lookup.
- def skip_default_locale!
- @skip_default_locale = true
- self.locale = nil
- end
-
# Override locale to return a symbol instead of array.
def locale
@details[:locale].first
@@ -236,7 +226,7 @@ module ActionView
config.locale = value
end
- super(@skip_default_locale ? I18n.locale : default_locale)
+ super(default_locale)
end
# Uses the first format in the formats array for layout lookup.
diff --git a/actionview/lib/action_view/model_naming.rb b/actionview/lib/action_view/model_naming.rb
index e09ebd60df..d42e436b17 100644
--- a/actionview/lib/action_view/model_naming.rb
+++ b/actionview/lib/action_view/model_naming.rb
@@ -6,7 +6,7 @@ module ActionView
end
def model_name_from_record_or_class(record_or_class)
- (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
+ convert_to_model(record_or_class).model_name
end
end
end
diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb
index 63f645431a..c8484bed34 100644
--- a/actionview/lib/action_view/record_identifier.rb
+++ b/actionview/lib/action_view/record_identifier.rb
@@ -2,29 +2,54 @@ require 'active_support/core_ext/module'
require 'action_view/model_naming'
module ActionView
- # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
- # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
- # a higher logical level.
+ # RecordIdentifier encapsulates methods used by various ActionView helpers
+ # to associate records with DOM elements.
#
- # # routes
- # resources :posts
+ # Consider for example the following code that displays the body of a post:
#
- # # view
- # <%= div_for(post) do %> <div id="post_45" class="post">
- # <%= post.body %> What a wonderful world!
- # <% end %> </div>
+ # <%= div_for(post) do %>
+ # <%= post.body %>
+ # <% end %>
#
- # # controller
- # def update
- # post = Post.find(params[:id])
- # post.update(params[:post])
+ # When +post+ is a new, unsaved ActiveRecord::Base intance, the resulting HTML
+ # is:
#
- # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
- # end
+ # <div id="new_post" class="post">
+ # </div>
+ #
+ # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML
+ # is:
+ #
+ # <div id="post_42" class="post">
+ # What a wonderful world!
+ # </div>
+ #
+ # In both cases, the +id+ and +class+ of the wrapping DOM element are
+ # automatically generated, following naming conventions encapsulated by the
+ # RecordIdentifier methods #dom_id and #dom_class:
+ #
+ # dom_id(Post.new) # => "new_post"
+ # dom_class(Post.new) # => "post"
+ # dom_id(Post.find 42) # => "post_42"
+ # dom_class(Post.find 42) # => "post"
#
- # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
- # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
- # same naming convention and allows you to write less code if you follow it.
+ # Note that these methods do not strictly require +Post+ to be a subclass of
+ # ActiveRecord::Base.
+ # Any +Post+ class will work as long as its instances respond to +to_key+
+ # and +model_name+, given that +model_name+ responds to +param_key+.
+ # For instance:
+ #
+ # class Post
+ # attr_accessor :to_key
+ #
+ # def model_name
+ # OpenStruct.new param_key: 'post'
+ # end
+ #
+ # def self.find(id)
+ # new.tap { |post| post.to_key = [id] }
+ # end
+ # end
module RecordIdentifier
extend self
extend ModelNaming
diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb
index 73c19a0ae2..1f122f9bc6 100644
--- a/actionview/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionview/lib/action_view/renderer/abstract_renderer.rb
@@ -29,8 +29,9 @@ module ActionView
def extract_details(options)
@lookup_context.registered_details.each_with_object({}) do |key, details|
- next unless value = options[key]
- details[key] = Array(value)
+ value = options[key]
+
+ details[key] = Array(value) if value
end
end
@@ -41,6 +42,7 @@ module ActionView
def prepend_formats(formats)
formats = Array(formats)
return if formats.empty? || @lookup_context.html_fallback_for_js
+
@lookup_context.formats = formats | @lookup_context.formats
end
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 36f17f01fd..610396506f 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -1,6 +1,33 @@
require 'thread_safe'
module ActionView
+ class PartialIteration
+ # The number of iterations that will be done by the partial.
+ attr_reader :size
+
+ # The current iteration of the partial.
+ attr_reader :index
+
+ def initialize(size)
+ @size = size
+ @index = 0
+ end
+
+ # Check if this is the first iteration of the partial.
+ def first?
+ index == 0
+ end
+
+ # Check if this is the last iteration of the partial.
+ def last?
+ index == size - 1
+ end
+
+ def iterate! # :nodoc:
+ @index += 1
+ end
+ end
+
# = Action View Partials
#
# There's also a convenience method for rendering sub templates within the current controller that depends on a
@@ -56,8 +83,12 @@ module ActionView
# <%= render partial: "ad", collection: @advertisements %>
#
# This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
- # iteration counter will automatically be made available to the template with a name of the form
- # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
+ # iteration object will automatically be made available to the template with a name of the form
+ # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in
+ # the collection and the total size of the collection. The iteration object also has two convenience methods,
+ # +first?+ and +last?+. In the case of the example above, the template would be fed +ad_iteration+.
+ # For backwards compatibility the +partial_name_counter+ is still present and is mapped to the iteration's
+ # +index+ method.
#
# The <tt>:as</tt> option may be used when rendering partials.
#
@@ -281,6 +312,8 @@ module ActionView
end
end
+ private
+
def render_collection
return nil if @collection.blank?
@@ -322,37 +355,41 @@ module ActionView
# respond to +to_partial_path+ in order to setup the path.
def setup(context, options, block)
@view = context
- partial = options[:partial]
-
@options = options
- @locals = options[:locals] || {}
@block = block
+
+ @locals = options[:locals] || {}
@details = extract_details(options)
prepend_formats(options[:formats])
+ partial = options[:partial]
+
if String === partial
+ @has_object = options.key?(:object)
@object = options[:object]
+ @collection = collection_from_options
@path = partial
- @collection = collection
else
+ @has_object = true
@object = partial
+ @collection = collection_from_object || collection_from_options
- if @collection = collection_from_object || collection
+ if @collection
paths = @collection_data = @collection.map { |o| partial_path(o) }
- @path = paths.uniq.size == 1 ? paths.first : nil
+ @path = paths.uniq.one? ? paths.first : nil
else
@path = partial_path
end
end
if as = options[:as]
- raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/
+ raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/
as = as.to_sym
end
if @path
- @variable, @variable_counter = retrieve_variable(@path, as)
+ @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
@template_keys = retrieve_template_keys
else
paths.map! { |path| retrieve_variable(path, as).unshift(path) }
@@ -361,7 +398,7 @@ module ActionView
self
end
- def collection
+ def collection_from_options
if @options.key?(:collection)
collection = @options[:collection]
collection.respond_to?(:to_ary) ? collection.to_ary : []
@@ -373,9 +410,7 @@ module ActionView
end
def find_partial
- if path = @path
- find_template(path, @template_keys)
- end
+ find_template(@path, @template_keys) if @path
end
def find_template(path, locals)
@@ -385,19 +420,22 @@ module ActionView
def collection_with_template
view, locals, template = @view, @locals, @template
- as, counter = @variable, @variable_counter
+ as, counter, iteration = @variable, @variable_counter, @variable_iteration
if layout = @options[:layout]
layout = find_template(layout, @template_keys)
end
- index = -1
+ partial_iteration = PartialIteration.new(@collection.size)
+ locals[iteration] = partial_iteration
+
@collection.map do |object|
- locals[as] = object
- locals[counter] = (index += 1)
+ locals[as] = object
+ locals[counter] = partial_iteration.index
content = template.render(view, locals)
content = layout.render(view, locals) { content } if layout
+ partial_iteration.iterate!
content
end
end
@@ -407,16 +445,20 @@ module ActionView
cache = {}
keys = @locals.keys
- index = -1
+ partial_iteration = PartialIteration.new(@collection.size)
+
@collection.map do |object|
- index += 1
- path, as, counter = collection_data[index]
+ index = partial_iteration.index
+ path, as, counter, iteration = collection_data[index]
- locals[as] = object
- locals[counter] = index
+ locals[as] = object
+ locals[counter] = index
+ locals[iteration] = partial_iteration
template = (cache[path] ||= find_template(path, keys + [as, counter]))
- template.render(view, locals)
+ content = template.render(view, locals)
+ partial_iteration.iterate!
+ content
end
end
@@ -466,8 +508,11 @@ module ActionView
def retrieve_template_keys
keys = @locals.keys
- keys << @variable if @object || @collection
- keys << @variable_counter if @collection
+ keys << @variable if @has_object || @collection
+ if @collection
+ keys << @variable_counter
+ keys << @variable_iteration
+ end
keys
end
@@ -477,16 +522,27 @@ module ActionView
raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
$1.to_sym
end
- variable_counter = :"#{variable}_counter" if @collection
- [variable, variable_counter]
+ if @collection
+ variable_counter = :"#{variable}_counter"
+ variable_iteration = :"#{variable}_iteration"
+ end
+ [variable, variable_counter, variable_iteration]
end
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
+ "make sure your partial name starts with underscore, " +
+ "and is followed by any combination of letters, numbers and underscores."
+
+ OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
"and is followed by any combination of letters, numbers and underscores."
def raise_invalid_identifier(path)
raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
end
+
+ def raise_invalid_option_as(as)
+ raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
+ end
end
end
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index be17097428..cd21d7ab47 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -6,20 +6,19 @@ module ActionView
@view = context
@details = extract_details(options)
template = determine_template(options)
- context = @lookup_context
prepend_formats(template.formats)
- unless context.rendered_format
- context.rendered_format = template.formats.first || formats.first
- end
+ @lookup_context.rendered_format ||= (template.formats.first || formats.first)
render_template(template, options[:layout], options[:locals])
end
+ private
+
# Determine the template to be rendered using the given options.
- def determine_template(options) #:nodoc:
- keys = options.fetch(:locals, {}).keys
+ def determine_template(options)
+ keys = options.has_key?(:locals) ? options[:locals].keys : []
if options.key?(:body)
Template::Text.new(options[:body])
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index c92d090cce..abd3b77c67 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -35,12 +35,13 @@ module ActionView
module ClassMethods
def view_context_class
@view_context_class ||= begin
- routes = respond_to?(:_routes) && _routes
+ supports_path = supports_path?
+ routes = respond_to?(:_routes) && _routes
helpers = respond_to?(:_helpers) && _helpers
Class.new(ActionView::Base) do
if routes
- include routes.url_helpers
+ include routes.url_helpers(supports_path)
include routes.mounted_helpers
end
@@ -107,7 +108,7 @@ module ActionView
end
# Normalize args by converting render "foo" to render :action => "foo" and
- # render "foo/bar" to render :file => "foo/bar".
+ # render "foo/bar" to render :template => "foo/bar".
# :api: private
def _normalize_args(action=nil, options={})
options = super(action, options)
@@ -117,7 +118,7 @@ module ActionView
options = action
when String, Symbol
action = action.to_s
- key = action.include?(?/) ? :file : :action
+ key = action.include?(?/) ? :template : :action
options[key] = action
else
options[:partial] = action
diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb
index 881a123572..f281333a41 100644
--- a/actionview/lib/action_view/routing_url_for.rb
+++ b/actionview/lib/action_view/routing_url_for.rb
@@ -80,19 +80,38 @@ module ActionView
when String
options
when nil
- super({:only_path => true})
+ super(only_path: _generate_paths_by_default)
when Hash
- super({ :only_path => options[:host].nil? }.merge!(options.symbolize_keys))
+ options = options.symbolize_keys
+ unless options.key?(:only_path)
+ if options[:host].nil?
+ options[:only_path] = _generate_paths_by_default
+ else
+ options[:only_path] = false
+ end
+ end
+
+ super(options)
when :back
_back_url
- when Symbol
- ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_string_call self, options
when Array
- polymorphic_path(options, options.extract_options!)
- when Class
- ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_class_call self, options
+ if _generate_paths_by_default
+ polymorphic_path(options, options.extract_options!)
+ else
+ polymorphic_url(options, options.extract_options!)
+ end
else
- ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call self, options
+ method = _generate_paths_by_default ? :path : :url
+ builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method)
+
+ case options
+ when Symbol
+ builder.handle_string_call(self, options)
+ when Class
+ builder.handle_class_call(self, options)
+ else
+ builder.handle_model_call(self, options)
+ end
end
end
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 9d39d02a37..6b61378a1f 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -242,7 +242,7 @@ module ActionView
end
instrument("!compile_template") do
- compile(view, mod)
+ compile(mod)
end
# Just discard the source if we have a virtual path. This
@@ -264,7 +264,7 @@ module ActionView
# encode the source into <tt>Encoding.default_internal</tt>.
# In general, this means that templates will be UTF-8 inside of Rails,
# regardless of the original source encoding.
- def compile(view, mod) #:nodoc:
+ def compile(mod) #:nodoc:
encode!
method_name = self.method_name
code = @handler.call(self)
@@ -293,18 +293,8 @@ module ActionView
raise WrongEncodingError.new(@source, Encoding.default_internal)
end
- begin
- mod.module_eval(source, identifier, 0)
- ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
- rescue => e # errors from template code
- if logger = (view && view.logger)
- logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
- logger.debug "Function body: #{source}"
- logger.debug "Backtrace: #{e.backtrace.join("\n")}"
- end
-
- raise ActionView::Template::Error.new(self, e)
- end
+ mod.module_eval(source, identifier, 0)
+ ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
end
def handle_render_error(view, e) #:nodoc:
@@ -323,15 +313,19 @@ module ActionView
def locals_code #:nodoc:
# Double assign to suppress the dreaded 'assigned but unused variable' warning
- @locals.map { |key| "#{key} = #{key} = local_assigns[:#{key}];" }.join
+ @locals.each_with_object('') { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" }
end
def method_name #:nodoc:
- @method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".gsub('-', "_")
+ @method_name ||= begin
+ m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
+ m.tr!('-', '_')
+ m
+ end
end
def identifier_method_name #:nodoc:
- inspect.gsub(/[^a-z_]/, '_')
+ inspect.tr('^a-z_', '_')
end
def instrument(action, &block)
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb
index 743ef6de0a..390bce98a2 100644
--- a/actionview/lib/action_view/template/error.rb
+++ b/actionview/lib/action_view/template/error.rb
@@ -75,7 +75,7 @@ module ActionView
def sub_template_message
if @sub_templates
"Trace of template inclusion: " +
- @sub_templates.collect { |template| template.inspect }.join(", ")
+ @sub_templates.collect(&:inspect).join(", ")
else
""
end
diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb
index 33bfcb458c..9e61ea4225 100644
--- a/actionview/lib/action_view/template/handlers.rb
+++ b/actionview/lib/action_view/template/handlers.rb
@@ -22,7 +22,7 @@ module ActionView #:nodoc:
# Register an object that knows how to handle template files with the given
# extensions. This can be used to implement new template types.
- # The handler must respond to `:call`, which will be passed the template
+ # The handler must respond to +:call+, which will be passed the template
# and should return the rendered template as a String.
def register_template_handler(*extensions, handler)
raise(ArgumentError, "Extension is required") if extensions.empty?
@@ -42,7 +42,7 @@ module ActionView #:nodoc:
end
def template_handler_extensions
- @@template_handlers.keys.map {|key| key.to_s }.sort
+ @@template_handlers.keys.map(&:to_s).sort
end
def registered_template_handler(extension)
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 4523060442..85a100ed4c 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -35,7 +35,7 @@ module ActionView
end
end
- BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
flush_newline_if_pending(src)
@@ -49,9 +49,9 @@ module ActionView
def add_expr_escaped(src, code)
flush_newline_if_pending(src)
if code =~ BLOCK_EXPR
- src << "@output_buffer.safe_append= " << code
+ src << "@output_buffer.safe_expr_append= " << code
else
- src << "@output_buffer.safe_append=(" << code << ");"
+ src << "@output_buffer.safe_expr_append=(" << code << ");"
end
end
diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb
index 0c0d1fffcb..397c86014a 100644
--- a/actionview/lib/action_view/template/handlers/raw.rb
+++ b/actionview/lib/action_view/template/handlers/raw.rb
@@ -2,7 +2,7 @@ module ActionView
module Template::Handlers
class Raw
def call(template)
- escaped = template.source.gsub(':', '\:')
+ escaped = template.source.gsub(/:/, '\:')
'%q:' + escaped + ':;'
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index d29d020c17..29d2e9ca90 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -1,6 +1,7 @@
require "pathname"
require "active_support/core_ext/class"
require "active_support/core_ext/module/attribute_accessors"
+require 'active_support/core_ext/string/filters'
require "action_view/template"
require "thread"
require "thread_safe"
@@ -138,7 +139,7 @@ module ActionView
# resolver is fresher before returning it.
def cached(key, path_info, details, locals) #:nodoc:
name, prefix, partial = path_info
- locals = locals.map { |x| x.to_s }.sort!
+ locals = locals.map(&:to_s).sort!
if key
@cache.cache(key, name, prefix, partial, locals) do
@@ -251,9 +252,10 @@ module ActionView
extension = pieces.pop
unless extension
- message = "The file #{path} did not specify a template handler. The default is currently ERB, " \
- "but will change to RAW in the future."
- ActiveSupport::Deprecation.warn message
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ The file #{path} did not specify a template handler. The default is
+ currently ERB, but will change to RAW in the future.
+ MSG
end
handler = Template.handler_for_extension(extension)
@@ -272,13 +274,13 @@ module ActionView
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
# looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
#
- # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
+ # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
#
# This one allows you to keep files with different formats in separate subdirectories,
# eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
# `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
#
- # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
+ # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
#
# If you don't specify a pattern then the default will be used.
#
@@ -287,7 +289,7 @@ module ActionView
#
# ActionController::Base.view_paths = FileSystemResolver.new(
# Rails.root.join("app/views"),
- # ":prefix{/:locale}/:action{.:formats,}{.:handlers,}"
+ # ":prefix{/:locale}/:action{.:formats,}{+:variants,}{.:handlers,}"
# )
#
# ==== Pattern format and variables
@@ -299,6 +301,7 @@ module ActionView
# * <tt>:action</tt> - name of the action
# * <tt>:locale</tt> - possible locale versions
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
+ # * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
# * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
#
class FileSystemResolver < PathResolver
diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb
index b84e0281ae..be45fcf742 100644
--- a/actionview/lib/action_view/template/types.rb
+++ b/actionview/lib/action_view/template/types.rb
@@ -9,7 +9,7 @@ module ActionView
self.types = Set.new
def self.register(*t)
- types.merge(t.map { |type| type.to_s })
+ types.merge(t.map(&:to_s))
end
register :html, :text, :js, :css, :xml, :json
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 9e8e6f43d5..812b011bd7 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -3,6 +3,8 @@ require 'action_controller'
require 'action_controller/test_case'
require 'action_view'
+require 'rails-dom-testing'
+
module ActionView
# = Action View Test Case
class TestCase < ActiveSupport::TestCase
@@ -34,6 +36,7 @@ module ActionView
extend ActiveSupport::Concern
include ActionDispatch::Assertions, ActionDispatch::TestProcess
+ include Rails::Dom::Testing::Assertions
include ActionController::TemplateAssertions
include ActionView::Context
@@ -99,7 +102,9 @@ module ActionView
def setup_with_controller
@controller = ActionView::TestCase::TestController.new
@request = @controller.request
- @output_buffer = ActiveSupport::SafeBuffer.new
+ # empty string ensures buffer has UTF-8 encoding as
+ # new without arguments returns ASCII-8BIT encoded buffer like String#new
+ @output_buffer = ActiveSupport::SafeBuffer.new ''
@rendered = ''
make_test_case_available_to_view!
@@ -120,6 +125,7 @@ module ActionView
@_rendered_views ||= RenderedViewsCollection.new
end
+ # Need to experiment if this priority is the best one: rendered => output_buffer
class RenderedViewsCollection
def initialize
@rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
@@ -151,11 +157,9 @@ module ActionView
private
- # Support the selector assertions
- #
# Need to experiment if this priority is the best one: rendered => output_buffer
- def response_from_page
- HTML::Document.new(@rendered.blank? ? @output_buffer : @rendered).root
+ def document_root_element
+ Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root
end
def say_no_to_protect_against_forgery!
@@ -236,7 +240,8 @@ module ActionView
:@test_passed,
:@view,
:@view_context_class,
- :@_subscribers
+ :@_subscribers,
+ :@html_document
]
def _user_defined_ivars
@@ -259,7 +264,7 @@ module ActionView
def method_missing(selector, *args)
if @controller.respond_to?(:_routes) &&
- ( @controller._routes.named_routes.helpers.include?(selector) ||
+ ( @controller._routes.named_routes.route_defined?(selector) ||
@controller._routes.mounted_helpers.method_defined?(selector) )
@controller.__send__(selector, *args)
else
diff --git a/actionview/lib/action_view/vendor/html-scanner.rb b/actionview/lib/action_view/vendor/html-scanner.rb
deleted file mode 100644
index 775b827529..0000000000
--- a/actionview/lib/action_view/vendor/html-scanner.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/html-scanner"
-
-module HTML
- extend ActiveSupport::Autoload
-
- eager_autoload do
- autoload :CDATA, 'html/node'
- autoload :Document, 'html/document'
- autoload :FullSanitizer, 'html/sanitizer'
- autoload :LinkSanitizer, 'html/sanitizer'
- autoload :Node, 'html/node'
- autoload :Sanitizer, 'html/sanitizer'
- autoload :Selector, 'html/selector'
- autoload :Tag, 'html/node'
- autoload :Text, 'html/node'
- autoload :Tokenizer, 'html/tokenizer'
- autoload :Version, 'html/version'
- autoload :WhiteListSanitizer, 'html/sanitizer'
- end
-end
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/document.rb b/actionview/lib/action_view/vendor/html-scanner/html/document.rb
deleted file mode 100644
index 386820300a..0000000000
--- a/actionview/lib/action_view/vendor/html-scanner/html/document.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'html/tokenizer'
-require 'html/node'
-require 'html/selector'
-require 'html/sanitizer'
-
-module HTML #:nodoc:
- # A top-level HTML document. You give it a body of text, and it will parse that
- # text into a tree of nodes.
- class Document #:nodoc:
-
- # The root of the parsed document.
- attr_reader :root
-
- # Create a new Document from the given text.
- def initialize(text, strict=false, xml=false)
- tokenizer = Tokenizer.new(text)
- @root = Node.new(nil)
- node_stack = [ @root ]
- while token = tokenizer.next
- node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict)
-
- node_stack.last.children << node unless node.tag? && node.closing == :close
- if node.tag?
- if node_stack.length > 1 && node.closing == :close
- if node_stack.last.name == node.name
- if node_stack.last.children.empty?
- node_stack.last.children << Text.new(node_stack.last, node.line, node.position, "")
- end
- node_stack.pop
- else
- open_start = node_stack.last.position - 20
- open_start = 0 if open_start < 0
- close_start = node.position - 20
- close_start = 0 if close_start < 0
- msg = <<EOF.strip
-ignoring attempt to close #{node_stack.last.name} with #{node.name}
- opened at byte #{node_stack.last.position}, line #{node_stack.last.line}
- closed at byte #{node.position}, line #{node.line}
- attributes at open: #{node_stack.last.attributes.inspect}
- text around open: #{text[open_start,40].inspect}
- text around close: #{text[close_start,40].inspect}
-EOF
- strict ? raise(msg) : warn(msg)
- end
- elsif !node.childless?(xml) && node.closing != :close
- node_stack.push node
- end
- end
- end
- end
-
- # Search the tree for (and return) the first node that matches the given
- # conditions. The conditions are interpreted differently for different node
- # types, see HTML::Text#find and HTML::Tag#find.
- def find(conditions)
- @root.find(conditions)
- end
-
- # Search the tree for (and return) all nodes that match the given
- # conditions. The conditions are interpreted differently for different node
- # types, see HTML::Text#find and HTML::Tag#find.
- def find_all(conditions)
- @root.find_all(conditions)
- end
-
- end
-
-end
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/node.rb b/actionview/lib/action_view/vendor/html-scanner/html/node.rb
deleted file mode 100644
index 27f0f2f6f8..0000000000
--- a/actionview/lib/action_view/vendor/html-scanner/html/node.rb
+++ /dev/null
@@ -1,532 +0,0 @@
-require 'strscan'
-
-module HTML #:nodoc:
-
- class Conditions < Hash #:nodoc:
- def initialize(hash)
- super()
- hash = { :content => hash } unless Hash === hash
- hash = keys_to_symbols(hash)
- hash.each do |k,v|
- case k
- when :tag, :content then
- # keys are valid, and require no further processing
- when :attributes then
- hash[k] = keys_to_strings(v)
- when :parent, :child, :ancestor, :descendant, :sibling, :before,
- :after
- hash[k] = Conditions.new(v)
- when :children
- hash[k] = v = keys_to_symbols(v)
- v.each do |key,value|
- case key
- when :count, :greater_than, :less_than
- # keys are valid, and require no further processing
- when :only
- v[key] = Conditions.new(value)
- else
- raise "illegal key #{key.inspect} => #{value.inspect}"
- end
- end
- else
- raise "illegal key #{k.inspect} => #{v.inspect}"
- end
- end
- update hash
- end
-
- private
-
- def keys_to_strings(hash)
- Hash[hash.keys.map {|k| [k.to_s, hash[k]]}]
- end
-
- def keys_to_symbols(hash)
- Hash[hash.keys.map do |k|
- raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym)
- [k.to_sym, hash[k]]
- end]
- end
- end
-
- # The base class of all nodes, textual and otherwise, in an HTML document.
- class Node #:nodoc:
- # The array of children of this node. Not all nodes have children.
- attr_reader :children
-
- # The parent node of this node. All nodes have a parent, except for the
- # root node.
- attr_reader :parent
-
- # The line number of the input where this node was begun
- attr_reader :line
-
- # The byte position in the input where this node was begun
- attr_reader :position
-
- # Create a new node as a child of the given parent.
- def initialize(parent, line=0, pos=0)
- @parent = parent
- @children = []
- @line, @position = line, pos
- end
-
- # Returns a textual representation of the node.
- def to_s
- @children.join()
- end
-
- # Returns false (subclasses must override this to provide specific matching
- # behavior.) +conditions+ may be of any type.
- def match(conditions)
- false
- end
-
- # Search the children of this node for the first node for which #find
- # returns non +nil+. Returns the result of the #find call that succeeded.
- def find(conditions)
- conditions = validate_conditions(conditions)
- @children.each do |child|
- node = child.find(conditions)
- return node if node
- end
- nil
- end
-
- # Search for all nodes that match the given conditions, and return them
- # as an array.
- def find_all(conditions)
- conditions = validate_conditions(conditions)
-
- matches = []
- matches << self if match(conditions)
- @children.each do |child|
- matches.concat child.find_all(conditions)
- end
- matches
- end
-
- # Returns +false+. Subclasses may override this if they define a kind of
- # tag.
- def tag?
- false
- end
-
- def validate_conditions(conditions)
- Conditions === conditions ? conditions : Conditions.new(conditions)
- end
-
- def ==(node)
- return false unless self.class == node.class && children.size == node.children.size
-
- equivalent = true
-
- children.size.times do |i|
- equivalent &&= children[i] == node.children[i]
- end
-
- equivalent
- end
-
- class <<self
- def parse(parent, line, pos, content, strict=true)
- if content !~ /^<\S/
- Text.new(parent, line, pos, content)
- else
- scanner = StringScanner.new(content)
-
- unless scanner.skip(/</)
- if strict
- raise "expected <"
- else
- return Text.new(parent, line, pos, content)
- end
- end
-
- if scanner.skip(/!\[CDATA\[/)
- unless scanner.skip_until(/\]\]>/)
- if strict
- raise "expected ]]> (got #{scanner.rest.inspect} for #{content})"
- else
- scanner.skip_until(/\Z/)
- end
- end
-
- return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
- end
-
- closing = ( scanner.scan(/\//) ? :close : nil )
- return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/)
- name.downcase!
-
- unless closing
- scanner.skip(/\s*/)
- attributes = {}
- while attr = scanner.scan(/[-\w:]+/)
- value = true
- if scanner.scan(/\s*=\s*/)
- if delim = scanner.scan(/['"]/)
- value = ""
- while text = scanner.scan(/[^#{delim}\\]+|./)
- case text
- when "\\" then
- value << text
- break if scanner.eos?
- value << scanner.getch
- when delim
- break
- else value << text
- end
- end
- else
- value = scanner.scan(/[^\s>\/]+/)
- end
- end
- attributes[attr.downcase] = value
- scanner.skip(/\s*/)
- end
-
- closing = ( scanner.scan(/\//) ? :self : nil )
- end
-
- unless scanner.scan(/\s*>/)
- if strict
- raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})"
- else
- # throw away all text until we find what we're looking for
- scanner.skip_until(/>/) or scanner.terminate
- end
- end
-
- Tag.new(parent, line, pos, name, attributes, closing)
- end
- end
- end
- end
-
- # A node that represents text, rather than markup.
- class Text < Node #:nodoc:
-
- attr_reader :content
-
- # Creates a new text node as a child of the given parent, with the given
- # content.
- def initialize(parent, line, pos, content)
- super(parent, line, pos)
- @content = content
- end
-
- # Returns the content of this node.
- def to_s
- @content
- end
-
- # Returns +self+ if this node meets the given conditions. Text nodes support
- # conditions of the following kinds:
- #
- # * if +conditions+ is a string, it must be a substring of the node's
- # content
- # * if +conditions+ is a regular expression, it must match the node's
- # content
- # * if +conditions+ is a hash, it must contain a <tt>:content</tt> key that
- # is either a string or a regexp, and which is interpreted as described
- # above.
- def find(conditions)
- match(conditions) && self
- end
-
- # Returns non-+nil+ if this node meets the given conditions, or +nil+
- # otherwise. See the discussion of #find for the valid conditions.
- def match(conditions)
- case conditions
- when String
- @content == conditions
- when Regexp
- @content =~ conditions
- when Hash
- conditions = validate_conditions(conditions)
-
- # Text nodes only have :content, :parent, :ancestor
- unless (conditions.keys - [:content, :parent, :ancestor]).empty?
- return false
- end
-
- match(conditions[:content])
- else
- nil
- end
- end
-
- def ==(node)
- return false unless super
- content == node.content
- end
- end
-
- # A CDATA node is simply a text node with a specialized way of displaying
- # itself.
- class CDATA < Text #:nodoc:
- def to_s
- "<![CDATA[#{super}]]>"
- end
- end
-
- # A Tag is any node that represents markup. It may be an opening tag, a
- # closing tag, or a self-closing tag. It has a name, and may have a hash of
- # attributes.
- class Tag < Node #:nodoc:
-
- # Either +nil+, <tt>:close</tt>, or <tt>:self</tt>
- attr_reader :closing
-
- # Either +nil+, or a hash of attributes for this node.
- attr_reader :attributes
-
- # The name of this tag.
- attr_reader :name
-
- # Create a new node as a child of the given parent, using the given content
- # to describe the node. It will be parsed and the node name, attributes and
- # closing status extracted.
- def initialize(parent, line, pos, name, attributes, closing)
- super(parent, line, pos)
- @name = name
- @attributes = attributes
- @closing = closing
- end
-
- # A convenience for obtaining an attribute of the node. Returns +nil+ if
- # the node has no attributes.
- def [](attr)
- @attributes ? @attributes[attr] : nil
- end
-
- # Returns non-+nil+ if this tag can contain child nodes.
- def childless?(xml = false)
- return false if xml && @closing.nil?
- !@closing.nil? ||
- @name =~ /^(img|br|hr|link|meta|area|base|basefont|
- col|frame|input|isindex|param)$/ox
- end
-
- # Returns a textual representation of the node
- def to_s
- if @closing == :close
- "</#{@name}>"
- else
- s = "<#{@name}"
- @attributes.each do |k,v|
- s << " #{k}"
- s << "=\"#{v}\"" if String === v
- end
- s << " /" if @closing == :self
- s << ">"
- @children.each { |child| s << child.to_s }
- s << "</#{@name}>" if @closing != :self && !@children.empty?
- s
- end
- end
-
- # If either the node or any of its children meet the given conditions, the
- # matching node is returned. Otherwise, +nil+ is returned. (See the
- # description of the valid conditions in the +match+ method.)
- def find(conditions)
- match(conditions) && self || super
- end
-
- # Returns +true+, indicating that this node represents an HTML tag.
- def tag?
- true
- end
-
- # Returns +true+ if the node meets any of the given conditions. The
- # +conditions+ parameter must be a hash of any of the following keys
- # (all are optional):
- #
- # * <tt>:tag</tt>: the node name must match the corresponding value
- # * <tt>:attributes</tt>: a hash. The node's values must match the
- # corresponding values in the hash.
- # * <tt>:parent</tt>: a hash. The node's parent must match the
- # corresponding hash.
- # * <tt>:child</tt>: a hash. At least one of the node's immediate children
- # must meet the criteria described by the hash.
- # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
- # meet the criteria described by the hash.
- # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
- # must meet the criteria described by the hash.
- # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
- # meet the criteria described by the hash.
- # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
- # the criteria described by the hash, and at least one sibling must match.
- # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
- # the criteria described by the hash, and at least one sibling must match.
- # * <tt>:children</tt>: a hash, for counting children of a node. Accepts the
- # keys:
- # ** <tt>:count</tt>: either a number or a range which must equal (or
- # include) the number of children that match.
- # ** <tt>:less_than</tt>: the number of matching children must be less than
- # this number.
- # ** <tt>:greater_than</tt>: the number of matching children must be
- # greater than this number.
- # ** <tt>:only</tt>: another hash consisting of the keys to use
- # to match on the children, and only matching children will be
- # counted.
- #
- # Conditions are matched using the following algorithm:
- #
- # * if the condition is a string, it must be a substring of the value.
- # * if the condition is a regexp, it must match the value.
- # * if the condition is a number, the value must match number.to_s.
- # * if the condition is +true+, the value must not be +nil+.
- # * if the condition is +false+ or +nil+, the value must be +nil+.
- #
- # Usage:
- #
- # # test if the node is a "span" tag
- # node.match tag: "span"
- #
- # # test if the node's parent is a "div"
- # node.match parent: { tag: "div" }
- #
- # # test if any of the node's ancestors are "table" tags
- # node.match ancestor: { tag: "table" }
- #
- # # test if any of the node's immediate children are "em" tags
- # node.match child: { tag: "em" }
- #
- # # test if any of the node's descendants are "strong" tags
- # node.match descendant: { tag: "strong" }
- #
- # # test if the node has between 2 and 4 span tags as immediate children
- # node.match children: { count: 2..4, only: { tag: "span" } }
- #
- # # get funky: test to see if the node is a "div", has a "ul" ancestor
- # # and an "li" parent (with "class" = "enum"), and whether or not it has
- # # a "span" descendant that contains # text matching /hello world/:
- # node.match tag: "div",
- # ancestor: { tag: "ul" },
- # parent: { tag: "li",
- # attributes: { class: "enum" } },
- # descendant: { tag: "span",
- # child: /hello world/ }
- def match(conditions)
- conditions = validate_conditions(conditions)
- # check content of child nodes
- if conditions[:content]
- if children.empty?
- return false unless match_condition("", conditions[:content])
- else
- return false unless children.find { |child| child.match(conditions[:content]) }
- end
- end
-
- # test the name
- return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]
-
- # test attributes
- (conditions[:attributes] || {}).each do |key, value|
- return false unless match_condition(self[key], value)
- end
-
- # test parent
- return false unless parent.match(conditions[:parent]) if conditions[:parent]
-
- # test children
- return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child]
-
- # test ancestors
- if conditions[:ancestor]
- return false unless catch :found do
- p = self
- throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
- end
- end
-
- # test descendants
- if conditions[:descendant]
- return false unless children.find do |child|
- # test the child
- child.match(conditions[:descendant]) ||
- # test the child's descendants
- child.match(:descendant => conditions[:descendant])
- end
- end
-
- # count children
- if opts = conditions[:children]
- matches = children.select do |c|
- (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
- end
-
- matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
- opts.each do |key, value|
- next if key == :only
- case key
- when :count
- if Integer === value
- return false if matches.length != value
- else
- return false unless value.include?(matches.length)
- end
- when :less_than
- return false unless matches.length < value
- when :greater_than
- return false unless matches.length > value
- else raise "unknown count condition #{key}"
- end
- end
- end
-
- # test siblings
- if conditions[:sibling] || conditions[:before] || conditions[:after]
- siblings = parent ? parent.children : []
- self_index = siblings.index(self)
-
- if conditions[:sibling]
- return false unless siblings.detect do |s|
- s != self && s.match(conditions[:sibling])
- end
- end
-
- if conditions[:before]
- return false unless siblings[self_index+1..-1].detect do |s|
- s != self && s.match(conditions[:before])
- end
- end
-
- if conditions[:after]
- return false unless siblings[0,self_index].detect do |s|
- s != self && s.match(conditions[:after])
- end
- end
- end
-
- true
- end
-
- def ==(node)
- return false unless super
- return false unless closing == node.closing && self.name == node.name
- attributes == node.attributes
- end
-
- private
- # Match the given value to the given condition.
- def match_condition(value, condition)
- case condition
- when String
- value && value == condition
- when Regexp
- value && value.match(condition)
- when Numeric
- value == condition.to_s
- when true
- !value.nil?
- when false, nil
- value.nil?
- else
- false
- end
- end
- end
-end
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb
deleted file mode 100644
index ed34eecf55..0000000000
--- a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb
+++ /dev/null
@@ -1,188 +0,0 @@
-require 'set'
-require 'cgi'
-require 'active_support/core_ext/module/attribute_accessors'
-
-module HTML
- class Sanitizer
- def sanitize(text, options = {})
- validate_options(options)
- return text unless sanitizeable?(text)
- tokenize(text, options).join
- end
-
- def sanitizeable?(text)
- !(text.nil? || text.empty? || !text.index("<"))
- end
-
- protected
- def tokenize(text, options)
- tokenizer = HTML::Tokenizer.new(text)
- result = []
- while token = tokenizer.next
- node = Node.parse(nil, 0, 0, token, false)
- process_node node, result, options
- end
- result
- end
-
- def process_node(node, result, options)
- result << node.to_s
- end
-
- def validate_options(options)
- if options[:tags] && !options[:tags].is_a?(Enumerable)
- raise ArgumentError, "You should pass :tags as an Enumerable"
- end
-
- if options[:attributes] && !options[:attributes].is_a?(Enumerable)
- raise ArgumentError, "You should pass :attributes as an Enumerable"
- end
- end
- end
-
- class FullSanitizer < Sanitizer
- def sanitize(text, options = {})
- result = super
- # strip any comments, and if they have a newline at the end (ie. line with
- # only a comment) strip that too
- result = result.gsub(/<!--(.*?)-->[\n]?/m, "") if (result && result =~ /<!--(.*?)-->[\n]?/m)
- # Recurse - handle all dirty nested tags
- result == text ? result : sanitize(result, options)
- end
-
- def process_node(node, result, options)
- result << node.to_s if node.class == HTML::Text
- end
- end
-
- class LinkSanitizer < FullSanitizer
- cattr_accessor :included_tags, :instance_writer => false
- self.included_tags = Set.new(%w(a href))
-
- def sanitizeable?(text)
- !(text.nil? || text.empty? || !((text.index("<a") || text.index("<href")) && text.index(">")))
- end
-
- protected
- def process_node(node, result, options)
- result << node.to_s unless node.is_a?(HTML::Tag) && included_tags.include?(node.name)
- end
- end
-
- class WhiteListSanitizer < Sanitizer
- [:protocol_separator, :uri_attributes, :allowed_attributes, :allowed_tags, :allowed_protocols, :bad_tags,
- :allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties].each do |attr|
- class_attribute attr, :instance_writer => false
- end
-
- # A regular expression of the valid characters used to separate protocols like
- # the ':' in 'http://foo.com'
- self.protocol_separator = /:|(&#0*58)|(&#x70)|(&#x0*3a)|(%|&#37;)3A/i
-
- # Specifies a Set of HTML attributes that can have URIs.
- self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc))
-
- # Specifies a Set of 'bad' tags that the #sanitize helper will remove completely, as opposed
- # to just escaping harmless tags like &lt;font&gt;
- self.bad_tags = Set.new(%w(script))
-
- # Specifies the default Set of tags that the #sanitize helper will allow unscathed.
- self.allowed_tags = Set.new(%w(strong em b i p code pre tt samp kbd var sub
- sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr
- acronym a img blockquote del ins))
-
- # Specifies the default Set of html attributes that the #sanitize helper will leave
- # in the allowed tag.
- self.allowed_attributes = Set.new(%w(href src width height alt cite datetime title class name xml:lang abbr))
-
- # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
- self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto
- feed svn urn aim rsync tag ssh sftp rtsp afs))
-
- # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
- self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse
- border-color border-left-color border-right-color border-top-color clear color cursor direction display
- elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height
- overflow pause pause-after pause-before pitch pitch-range richness speak speak-header speak-numeral speak-punctuation
- speech-rate stress text-align text-decoration text-indent unicode-bidi vertical-align voice-family volume white-space
- width))
-
- # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
- self.allowed_css_keywords = Set.new(%w(auto aqua black block blue bold both bottom brown center
- collapse dashed dotted fuchsia gray green !important italic left lime maroon medium none navy normal
- nowrap olive pointer purple red right solid silver teal top transparent underline white yellow))
-
- # Specifies the default Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers.
- self.shorthand_css_properties = Set.new(%w(background border margin padding))
-
- # Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
- def sanitize_css(style)
- # disallow urls
- style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
-
- # gauntlet
- if style !~ /\A([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*\z/ ||
- style !~ /\A(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*\z/
- return ''
- end
-
- clean = []
- style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val|
- if allowed_css_properties.include?(prop.downcase)
- clean << prop + ': ' + val + ';'
- elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
- unless val.split().any? do |keyword|
- !allowed_css_keywords.include?(keyword) &&
- keyword !~ /\A(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)\z/
- end
- clean << prop + ': ' + val + ';'
- end
- end
- end
- clean.join(' ')
- end
-
- protected
- def tokenize(text, options)
- options[:parent] = []
- options[:attributes] ||= allowed_attributes
- options[:tags] ||= allowed_tags
- super
- end
-
- def process_node(node, result, options)
- result << case node
- when HTML::Tag
- if node.closing == :close
- options[:parent].shift
- else
- options[:parent].unshift node.name
- end
-
- process_attributes_for node, options
-
- options[:tags].include?(node.name) ? node : nil
- else
- bad_tags.include?(options[:parent].first) ? nil : node.to_s.gsub(/</, "&lt;")
- end
- end
-
- def process_attributes_for(node, options)
- return unless node.attributes
- node.attributes.keys.each do |attr_name|
- value = node.attributes[attr_name].to_s
-
- if !options[:attributes].include?(attr_name) || contains_bad_protocols?(attr_name, value)
- node.attributes.delete(attr_name)
- else
- node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(CGI::unescapeHTML(value))
- end
- end
- end
-
- def contains_bad_protocols?(attr_name, value)
- uri_attributes.include?(attr_name) &&
- (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(&#x0*3a)|(%|&#37;)3A/i && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
- end
- end
-end
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/selector.rb b/actionview/lib/action_view/vendor/html-scanner/html/selector.rb
deleted file mode 100644
index dfdd724b9b..0000000000
--- a/actionview/lib/action_view/vendor/html-scanner/html/selector.rb
+++ /dev/null
@@ -1,830 +0,0 @@
-#--
-# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
-# Under MIT and/or CC By license.
-#++
-
-module HTML
-
- # Selects HTML elements using CSS 2 selectors.
- #
- # The +Selector+ class uses CSS selector expressions to match and select
- # HTML elements.
- #
- # For example:
- # selector = HTML::Selector.new "form.login[action=/login]"
- # creates a new selector that matches any +form+ element with the class
- # +login+ and an attribute +action+ with the value <tt>/login</tt>.
- #
- # === Matching Elements
- #
- # Use the #match method to determine if an element matches the selector.
- #
- # For simple selectors, the method returns an array with that element,
- # or +nil+ if the element does not match. For complex selectors (see below)
- # the method returns an array with all matched elements, of +nil+ if no
- # match found.
- #
- # For example:
- # if selector.match(element)
- # puts "Element is a login form"
- # end
- #
- # === Selecting Elements
- #
- # Use the #select method to select all matching elements starting with
- # one element and going through all children in depth-first order.
- #
- # This method returns an array of all matching elements, an empty array
- # if no match is found
- #
- # For example:
- # selector = HTML::Selector.new "input[type=text]"
- # matches = selector.select(element)
- # matches.each do |match|
- # puts "Found text field with name #{match.attributes['name']}"
- # end
- #
- # === Expressions
- #
- # Selectors can match elements using any of the following criteria:
- # * <tt>name</tt> -- Match an element based on its name (tag name).
- # For example, <tt>p</tt> to match a paragraph. You can use <tt>*</tt>
- # to match any element.
- # * <tt>#</tt><tt>id</tt> -- Match an element based on its identifier (the
- # <tt>id</tt> attribute). For example, <tt>#</tt><tt>page</tt>.
- # * <tt>.class</tt> -- Match an element based on its class name, all
- # class names if more than one specified.
- # * <tt>[attr]</tt> -- Match an element that has the specified attribute.
- # * <tt>[attr=value]</tt> -- Match an element that has the specified
- # attribute and value. (More operators are supported see below)
- # * <tt>:pseudo-class</tt> -- Match an element based on a pseudo class,
- # such as <tt>:nth-child</tt> and <tt>:empty</tt>.
- # * <tt>:not(expr)</tt> -- Match an element that does not match the
- # negation expression.
- #
- # When using a combination of the above, the element name comes first
- # followed by identifier, class names, attributes, pseudo classes and
- # negation in any order. Do not separate these parts with spaces!
- # Space separation is used for descendant selectors.
- #
- # For example:
- # selector = HTML::Selector.new "form.login[action=/login]"
- # The matched element must be of type +form+ and have the class +login+.
- # It may have other classes, but the class +login+ is required to match.
- # It must also have an attribute called +action+ with the value
- # <tt>/login</tt>.
- #
- # This selector will match the following element:
- # <form class="login form" method="post" action="/login">
- # but will not match the element:
- # <form method="post" action="/logout">
- #
- # === Attribute Values
- #
- # Several operators are supported for matching attributes:
- # * <tt>name</tt> -- The element must have an attribute with that name.
- # * <tt>name=value</tt> -- The element must have an attribute with that
- # name and value.
- # * <tt>name^=value</tt> -- The attribute value must start with the
- # specified value.
- # * <tt>name$=value</tt> -- The attribute value must end with the
- # specified value.
- # * <tt>name*=value</tt> -- The attribute value must contain the
- # specified value.
- # * <tt>name~=word</tt> -- The attribute value must contain the specified
- # word (space separated).
- # * <tt>name|=word</tt> -- The attribute value must start with specified
- # word.
- #
- # For example, the following two selectors match the same element:
- # #my_id
- # [id=my_id]
- # and so do the following two selectors:
- # .my_class
- # [class~=my_class]
- #
- # === Alternatives, siblings, children
- #
- # Complex selectors use a combination of expressions to match elements:
- # * <tt>expr1 expr2</tt> -- Match any element against the second expression
- # if it has some parent element that matches the first expression.
- # * <tt>expr1 > expr2</tt> -- Match any element against the second expression
- # if it is the child of an element that matches the first expression.
- # * <tt>expr1 + expr2</tt> -- Match any element against the second expression
- # if it immediately follows an element that matches the first expression.
- # * <tt>expr1 ~ expr2</tt> -- Match any element against the second expression
- # that comes after an element that matches the first expression.
- # * <tt>expr1, expr2</tt> -- Match any element against the first expression,
- # or against the second expression.
- #
- # Since children and sibling selectors may match more than one element given
- # the first element, the #match method may return more than one match.
- #
- # === Pseudo classes
- #
- # Pseudo classes were introduced in CSS 3. They are most often used to select
- # elements in a given position:
- # * <tt>:root</tt> -- Match the element only if it is the root element
- # (no parent element).
- # * <tt>:empty</tt> -- Match the element only if it has no child elements,
- # and no text content.
- # * <tt>:content(string)</tt> -- Match the element only if it has <tt>string</tt>
- # as its text content (ignoring leading and trailing whitespace).
- # * <tt>:only-child</tt> -- Match the element if it is the only child (element)
- # of its parent element.
- # * <tt>:only-of-type</tt> -- Match the element if it is the only child (element)
- # of its parent element and its type.
- # * <tt>:first-child</tt> -- Match the element if it is the first child (element)
- # of its parent element.
- # * <tt>:first-of-type</tt> -- Match the element if it is the first child (element)
- # of its parent element of its type.
- # * <tt>:last-child</tt> -- Match the element if it is the last child (element)
- # of its parent element.
- # * <tt>:last-of-type</tt> -- Match the element if it is the last child (element)
- # of its parent element of its type.
- # * <tt>:nth-child(b)</tt> -- Match the element if it is the b-th child (element)
- # of its parent element. The value <tt>b</tt> specifies its index, starting with 1.
- # * <tt>:nth-child(an+b)</tt> -- Match the element if it is the b-th child (element)
- # in each group of <tt>a</tt> child elements of its parent element.
- # * <tt>:nth-child(-an+b)</tt> -- Match the element if it is the first child (element)
- # in each group of <tt>a</tt> child elements, up to the first <tt>b</tt> child
- # elements of its parent element.
- # * <tt>:nth-child(odd)</tt> -- Match element in the odd position (i.e. first, third).
- # Same as <tt>:nth-child(2n+1)</tt>.
- # * <tt>:nth-child(even)</tt> -- Match element in the even position (i.e. second,
- # fourth). Same as <tt>:nth-child(2n+2)</tt>.
- # * <tt>:nth-of-type(..)</tt> -- As above, but only counts elements of its type.
- # * <tt>:nth-last-child(..)</tt> -- As above, but counts from the last child.
- # * <tt>:nth-last-of-type(..)</tt> -- As above, but counts from the last child and
- # only elements of its type.
- # * <tt>:not(selector)</tt> -- Match the element only if the element does not
- # match the simple selector.
- #
- # As you can see, <tt>:nth-child</tt> pseudo class and its variant can get quite
- # tricky and the CSS specification doesn't do a much better job explaining it.
- # But after reading the examples and trying a few combinations, it's easy to
- # figure out.
- #
- # For example:
- # table tr:nth-child(odd)
- # Selects every second row in the table starting with the first one.
- #
- # div p:nth-child(4)
- # Selects the fourth paragraph in the +div+, but not if the +div+ contains
- # other elements, since those are also counted.
- #
- # div p:nth-of-type(4)
- # Selects the fourth paragraph in the +div+, counting only paragraphs, and
- # ignoring all other elements.
- #
- # div p:nth-of-type(-n+4)
- # Selects the first four paragraphs, ignoring all others.
- #
- # And you can always select an element that matches one set of rules but
- # not another using <tt>:not</tt>. For example:
- # p:not(.post)
- # Matches all paragraphs that do not have the class <tt>.post</tt>.
- #
- # === Substitution Values
- #
- # You can use substitution with identifiers, class names and element values.
- # A substitution takes the form of a question mark (<tt>?</tt>) and uses the
- # next value in the argument list following the CSS expression.
- #
- # The substitution value may be a string or a regular expression. All other
- # values are converted to strings.
- #
- # For example:
- # selector = HTML::Selector.new "#?", /^\d+$/
- # matches any element whose identifier consists of one or more digits.
- #
- # See http://www.w3.org/TR/css3-selectors/
- class Selector
-
-
- # An invalid selector.
- class InvalidSelectorError < StandardError #:nodoc:
- end
-
-
- class << self
-
- # :call-seq:
- # Selector.for_class(cls) => selector
- #
- # Creates a new selector for the given class name.
- def for_class(cls)
- self.new([".?", cls])
- end
-
-
- # :call-seq:
- # Selector.for_id(id) => selector
- #
- # Creates a new selector for the given id.
- def for_id(id)
- self.new(["#?", id])
- end
-
- end
-
-
- # :call-seq:
- # Selector.new(string, [values ...]) => selector
- #
- # Creates a new selector from a CSS 2 selector expression.
- #
- # The first argument is the selector expression. All other arguments
- # are used for value substitution.
- #
- # Throws InvalidSelectorError is the selector expression is invalid.
- def initialize(selector, *values)
- raise ArgumentError, "CSS expression cannot be empty" if selector.empty?
- @source = ""
- values = values[0] if values.size == 1 && values[0].is_a?(Array)
-
- # We need a copy to determine if we failed to parse, and also
- # preserve the original pass by-ref statement.
- statement = selector.strip.dup
-
- # Create a simple selector, along with negation.
- simple_selector(statement, values).each { |name, value| instance_variable_set("@#{name}", value) }
-
- @alternates = []
- @depends = nil
-
- # Alternative selector.
- if statement.sub!(/^\s*,\s*/, "")
- second = Selector.new(statement, values)
- @alternates << second
- # If there are alternate selectors, we group them in the top selector.
- if alternates = second.instance_variable_get(:@alternates)
- second.instance_variable_set(:@alternates, [])
- @alternates.concat alternates
- end
- @source << " , " << second.to_s
- # Sibling selector: create a dependency into second selector that will
- # match element immediately following this one.
- elsif statement.sub!(/^\s*\+\s*/, "")
- second = next_selector(statement, values)
- @depends = lambda do |element, first|
- if element = next_element(element)
- second.match(element, first)
- end
- end
- @source << " + " << second.to_s
- # Adjacent selector: create a dependency into second selector that will
- # match all elements following this one.
- elsif statement.sub!(/^\s*~\s*/, "")
- second = next_selector(statement, values)
- @depends = lambda do |element, first|
- matches = []
- while element = next_element(element)
- if subset = second.match(element, first)
- if first && !subset.empty?
- matches << subset.first
- break
- else
- matches.concat subset
- end
- end
- end
- matches.empty? ? nil : matches
- end
- @source << " ~ " << second.to_s
- # Child selector: create a dependency into second selector that will
- # match a child element of this one.
- elsif statement.sub!(/^\s*>\s*/, "")
- second = next_selector(statement, values)
- @depends = lambda do |element, first|
- matches = []
- element.children.each do |child|
- if child.tag? && subset = second.match(child, first)
- if first && !subset.empty?
- matches << subset.first
- break
- else
- matches.concat subset
- end
- end
- end
- matches.empty? ? nil : matches
- end
- @source << " > " << second.to_s
- # Descendant selector: create a dependency into second selector that
- # will match all descendant elements of this one. Note,
- elsif statement =~ /^\s+\S+/ && statement != selector
- second = next_selector(statement, values)
- @depends = lambda do |element, first|
- matches = []
- stack = element.children.reverse
- while node = stack.pop
- next unless node.tag?
- if subset = second.match(node, first)
- if first && !subset.empty?
- matches << subset.first
- break
- else
- matches.concat subset
- end
- elsif children = node.children
- stack.concat children.reverse
- end
- end
- matches.empty? ? nil : matches
- end
- @source << " " << second.to_s
- else
- # The last selector is where we check that we parsed
- # all the parts.
- unless statement.empty? || statement.strip.empty?
- raise ArgumentError, "Invalid selector: #{statement}"
- end
- end
- end
-
-
- # :call-seq:
- # match(element, first?) => array or nil
- #
- # Matches an element against the selector.
- #
- # For a simple selector this method returns an array with the
- # element if the element matches, nil otherwise.
- #
- # For a complex selector (sibling and descendant) this method
- # returns an array with all matching elements, nil if no match is
- # found.
- #
- # Use +first_only=true+ if you are only interested in the first element.
- #
- # For example:
- # if selector.match(element)
- # puts "Element is a login form"
- # end
- def match(element, first_only = false)
- # Match element if no element name or element name same as element name
- if matched = (!@tag_name || @tag_name == element.name)
- # No match if one of the attribute matches failed
- for attr in @attributes
- if element.attributes[attr[0]] !~ attr[1]
- matched = false
- break
- end
- end
- end
-
- # Pseudo class matches (nth-child, empty, etc).
- if matched
- for pseudo in @pseudo
- unless pseudo.call(element)
- matched = false
- break
- end
- end
- end
-
- # Negation. Same rules as above, but we fail if a match is made.
- if matched && @negation
- for negation in @negation
- if negation[:tag_name] == element.name
- matched = false
- else
- for attr in negation[:attributes]
- if element.attributes[attr[0]] =~ attr[1]
- matched = false
- break
- end
- end
- end
- if matched
- for pseudo in negation[:pseudo]
- if pseudo.call(element)
- matched = false
- break
- end
- end
- end
- break unless matched
- end
- end
-
- # If element matched but depends on another element (child,
- # sibling, etc), apply the dependent matches instead.
- if matched && @depends
- matches = @depends.call(element, first_only)
- else
- matches = matched ? [element] : nil
- end
-
- # If this selector is part of the group, try all the alternative
- # selectors (unless first_only).
- if !first_only || !matches
- @alternates.each do |alternate|
- break if matches && first_only
- if subset = alternate.match(element, first_only)
- if matches
- matches.concat subset
- else
- matches = subset
- end
- end
- end
- end
-
- matches
- end
-
-
- # :call-seq:
- # select(root) => array
- #
- # Selects and returns an array with all matching elements, beginning
- # with one node and traversing through all children depth-first.
- # Returns an empty array if no match is found.
- #
- # The root node may be any element in the document, or the document
- # itself.
- #
- # For example:
- # selector = HTML::Selector.new "input[type=text]"
- # matches = selector.select(element)
- # matches.each do |match|
- # puts "Found text field with name #{match.attributes['name']}"
- # end
- def select(root)
- matches = []
- stack = [root]
- while node = stack.pop
- if node.tag? && subset = match(node, false)
- subset.each do |match|
- matches << match unless matches.any? { |item| item.equal?(match) }
- end
- elsif children = node.children
- stack.concat children.reverse
- end
- end
- matches
- end
-
-
- # Similar to #select but returns the first matching element. Returns +nil+
- # if no element matches the selector.
- def select_first(root)
- stack = [root]
- while node = stack.pop
- if node.tag? && subset = match(node, true)
- return subset.first if !subset.empty?
- elsif children = node.children
- stack.concat children.reverse
- end
- end
- nil
- end
-
-
- def to_s #:nodoc:
- @source
- end
-
-
- # Returns the next element after this one. Skips sibling text nodes.
- #
- # With the +name+ argument, returns the next element with that name,
- # skipping other sibling elements.
- def next_element(element, name = nil)
- if siblings = element.parent.children
- found = false
- siblings.each do |node|
- if node.equal?(element)
- found = true
- elsif found && node.tag?
- return node if (name.nil? || node.name == name)
- end
- end
- end
- nil
- end
-
-
- protected
-
-
- # Creates a simple selector given the statement and array of
- # substitution values.
- #
- # Returns a hash with the values +tag_name+, +attributes+,
- # +pseudo+ (classes) and +negation+.
- #
- # Called the first time with +can_negate+ true to allow
- # negation. Called a second time with false since negation
- # cannot be negated.
- def simple_selector(statement, values, can_negate = true)
- tag_name = nil
- attributes = []
- pseudo = []
- negation = []
-
- # Element name. (Note that in negation, this can come at
- # any order, but for simplicity we allow if only first).
- statement.sub!(/^(\*|[[:alpha:]][\w\-]*)/) do |match|
- match.strip!
- tag_name = match.downcase unless match == "*"
- @source << match
- "" # Remove
- end
-
- # Get identifier, class, attribute name, pseudo or negation.
- while true
- # Element identifier.
- next if statement.sub!(/^#(\?|[\w\-]+)/) do
- id = $1
- if id == "?"
- id = values.shift
- end
- @source << "##{id}"
- id = Regexp.new("^#{Regexp.escape(id.to_s)}$") unless id.is_a?(Regexp)
- attributes << ["id", id]
- "" # Remove
- end
-
- # Class name.
- next if statement.sub!(/^\.([\w\-]+)/) do
- class_name = $1
- @source << ".#{class_name}"
- class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp)
- attributes << ["class", class_name]
- "" # Remove
- end
-
- # Attribute value.
- next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do
- name, equality, value = $1, $2, $3
- if value == "?"
- value = values.shift
- else
- # Handle single and double quotes.
- value.strip!
- if (value[0] == ?" || value[0] == ?') && value[0] == value[-1]
- value = value[1..-2]
- end
- end
- @source << "[#{name}#{equality}'#{value}']"
- attributes << [name.downcase.strip, attribute_match(equality, value)]
- "" # Remove
- end
-
- # Root element only.
- next if statement.sub!(/^:root/) do
- pseudo << lambda do |element|
- element.parent.nil? || !element.parent.tag?
- end
- @source << ":root"
- "" # Remove
- end
-
- # Nth-child including last and of-type.
- next if statement.sub!(/^:nth-(last-)?(child|of-type)\((odd|even|(\d+|\?)|(-?\d*|\?)?n([+\-]\d+|\?)?)\)/) do |match|
- reverse = $1 == "last-"
- of_type = $2 == "of-type"
- @source << ":nth-#{$1}#{$2}("
- case $3
- when "odd"
- pseudo << nth_child(2, 1, of_type, reverse)
- @source << "odd)"
- when "even"
- pseudo << nth_child(2, 2, of_type, reverse)
- @source << "even)"
- when /^(\d+|\?)$/ # b only
- b = ($1 == "?" ? values.shift : $1).to_i
- pseudo << nth_child(0, b, of_type, reverse)
- @source << "#{b})"
- when /^(-?\d*|\?)?n([+\-]\d+|\?)?$/
- a = ($1 == "?" ? values.shift :
- $1 == "" ? 1 : $1 == "-" ? -1 : $1).to_i
- b = ($2 == "?" ? values.shift : $2).to_i
- pseudo << nth_child(a, b, of_type, reverse)
- @source << (b >= 0 ? "#{a}n+#{b})" : "#{a}n#{b})")
- else
- raise ArgumentError, "Invalid nth-child #{match}"
- end
- "" # Remove
- end
- # First/last child (of type).
- next if statement.sub!(/^:(first|last)-(child|of-type)/) do
- reverse = $1 == "last"
- of_type = $2 == "of-type"
- pseudo << nth_child(0, 1, of_type, reverse)
- @source << ":#{$1}-#{$2}"
- "" # Remove
- end
- # Only child (of type).
- next if statement.sub!(/^:only-(child|of-type)/) do
- of_type = $1 == "of-type"
- pseudo << only_child(of_type)
- @source << ":only-#{$1}"
- "" # Remove
- end
-
- # Empty: no child elements or meaningful content (whitespaces
- # are ignored).
- next if statement.sub!(/^:empty/) do
- pseudo << lambda do |element|
- empty = true
- for child in element.children
- if child.tag? || !child.content.strip.empty?
- empty = false
- break
- end
- end
- empty
- end
- @source << ":empty"
- "" # Remove
- end
- # Content: match the text content of the element, stripping
- # leading and trailing spaces.
- next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do
- content = $1
- if content == "?"
- content = values.shift
- elsif (content[0] == ?" || content[0] == ?') && content[0] == content[-1]
- content = content[1..-2]
- end
- @source << ":content('#{content}')"
- content = Regexp.new("^#{Regexp.escape(content.to_s)}$") unless content.is_a?(Regexp)
- pseudo << lambda do |element|
- text = ""
- for child in element.children
- unless child.tag?
- text << child.content
- end
- end
- text.strip =~ content
- end
- "" # Remove
- end
-
- # Negation. Create another simple selector to handle it.
- if statement.sub!(/^:not\(\s*/, "")
- raise ArgumentError, "Double negatives are not missing feature" unless can_negate
- @source << ":not("
- negation << simple_selector(statement, values, false)
- raise ArgumentError, "Negation not closed" unless statement.sub!(/^\s*\)/, "")
- @source << ")"
- next
- end
-
- # No match: moving on.
- break
- end
-
- # Return hash. The keys are mapped to instance variables.
- {:tag_name=>tag_name, :attributes=>attributes, :pseudo=>pseudo, :negation=>negation}
- end
-
-
- # Create a regular expression to match an attribute value based
- # on the equality operator (=, ^=, |=, etc).
- def attribute_match(equality, value)
- regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
- case equality
- when "=" then
- # Match the attribute value in full
- Regexp.new("^#{regexp}$")
- when "~=" then
- # Match a space-separated word within the attribute value
- Regexp.new("(^|\s)#{regexp}($|\s)")
- when "^="
- # Match the beginning of the attribute value
- Regexp.new("^#{regexp}")
- when "$="
- # Match the end of the attribute value
- Regexp.new("#{regexp}$")
- when "*="
- # Match substring of the attribute value
- regexp.is_a?(Regexp) ? regexp : Regexp.new(regexp)
- when "|=" then
- # Match the first space-separated item of the attribute value
- Regexp.new("^#{regexp}($|\s)")
- else
- raise InvalidSelectorError, "Invalid operation/value" unless value.empty?
- # Match all attributes values (existence check)
- //
- end
- end
-
-
- # Returns a lambda that can match an element against the nth-child
- # pseudo class, given the following arguments:
- # * +a+ -- Value of a part.
- # * +b+ -- Value of b part.
- # * +of_type+ -- True to test only elements of this type (of-type).
- # * +reverse+ -- True to count in reverse order (last-).
- def nth_child(a, b, of_type, reverse)
- # a = 0 means select at index b, if b = 0 nothing selected
- return lambda { |element| false } if a == 0 && b == 0
- # a < 0 and b < 0 will never match against an index
- return lambda { |element| false } if a < 0 && b < 0
- b = a + b + 1 if b < 0 # b < 0 just picks last element from each group
- b -= 1 unless b == 0 # b == 0 is same as b == 1, otherwise zero based
- lambda do |element|
- # Element must be inside parent element.
- return false unless element.parent && element.parent.tag?
- index = 0
- # Get siblings, reverse if counting from last.
- siblings = element.parent.children
- siblings = siblings.reverse if reverse
- # Match element name if of-type, otherwise ignore name.
- name = of_type ? element.name : nil
- found = false
- for child in siblings
- # Skip text nodes/comments.
- if child.tag? && (name == nil || child.name == name)
- if a == 0
- # Shortcut when a == 0 no need to go past count
- if index == b
- found = child.equal?(element)
- break
- end
- elsif a < 0
- # Only look for first b elements
- break if index > b
- if child.equal?(element)
- found = (index % a) == 0
- break
- end
- else
- # Otherwise, break if child found and count == an+b
- if child.equal?(element)
- found = (index % a) == b
- break
- end
- end
- index += 1
- end
- end
- found
- end
- end
-
-
- # Creates a only child lambda. Pass +of-type+ to only look at
- # elements of its type.
- def only_child(of_type)
- lambda do |element|
- # Element must be inside parent element.
- return false unless element.parent && element.parent.tag?
- name = of_type ? element.name : nil
- other = false
- for child in element.parent.children
- # Skip text nodes/comments.
- if child.tag? && (name == nil || child.name == name)
- unless child.equal?(element)
- other = true
- break
- end
- end
- end
- !other
- end
- end
-
-
- # Called to create a dependent selector (sibling, descendant, etc).
- # Passes the remainder of the statement that will be reduced to zero
- # eventually, and array of substitution values.
- #
- # This method is called from four places, so it helps to put it here
- # for reuse. The only logic deals with the need to detect comma
- # separators (alternate) and apply them to the selector group of the
- # top selector.
- def next_selector(statement, values)
- second = Selector.new(statement, values)
- # If there are alternate selectors, we group them in the top selector.
- if alternates = second.instance_variable_get(:@alternates)
- second.instance_variable_set(:@alternates, [])
- @alternates.concat alternates
- end
- second
- end
-
- end
-
-
- # See HTML::Selector.new
- def self.selector(statement, *values)
- Selector.new(statement, *values)
- end
-
-
- class Tag
-
- def select(selector, *values)
- selector = HTML::Selector.new(selector, values)
- selector.select(self)
- end
-
- end
-
-end
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb
deleted file mode 100644
index adf4e45930..0000000000
--- a/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb
+++ /dev/null
@@ -1,107 +0,0 @@
-require 'strscan'
-
-module HTML #:nodoc:
-
- # A simple HTML tokenizer. It simply breaks a stream of text into tokens, where each
- # token is a string. Each string represents either "text", or an HTML element.
- #
- # This currently assumes valid XHTML, which means no free < or > characters.
- #
- # Usage:
- #
- # tokenizer = HTML::Tokenizer.new(text)
- # while token = tokenizer.next
- # p token
- # end
- class Tokenizer #:nodoc:
-
- # The current (byte) position in the text
- attr_reader :position
-
- # The current line number
- attr_reader :line
-
- # Create a new Tokenizer for the given text.
- def initialize(text)
- text.encode!
- @scanner = StringScanner.new(text)
- @position = 0
- @line = 0
- @current_line = 1
- end
-
- # Returns the next token in the sequence, or +nil+ if there are no more tokens in
- # the stream.
- def next
- return nil if @scanner.eos?
- @position = @scanner.pos
- @line = @current_line
- if @scanner.check(/<\S/)
- update_current_line(scan_tag)
- else
- update_current_line(scan_text)
- end
- end
-
- private
-
- # Treat the text at the current position as a tag, and scan it. Supports
- # comments, doctype tags, and regular tags, and ignores less-than and
- # greater-than characters within quoted strings.
- def scan_tag
- tag = @scanner.getch
- if @scanner.scan(/!--/) # comment
- tag << @scanner.matched
- tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/))
- elsif @scanner.scan(/!\[CDATA\[/)
- tag << @scanner.matched
- tag << (@scanner.scan_until(/\]\]>/) || @scanner.scan_until(/\Z/))
- elsif @scanner.scan(/!/) # doctype
- tag << @scanner.matched
- tag << consume_quoted_regions
- else
- tag << consume_quoted_regions
- end
- tag
- end
-
- # Scan all text up to the next < character and return it.
- def scan_text
- "#{@scanner.getch}#{@scanner.scan(/[^<]*/)}"
- end
-
- # Counts the number of newlines in the text and updates the current line
- # accordingly.
- def update_current_line(text)
- text.scan(/\r?\n/) { @current_line += 1 }
- end
-
- # Skips over quoted strings, so that less-than and greater-than characters
- # within the strings are ignored.
- def consume_quoted_regions
- text = ""
- loop do
- match = @scanner.scan_until(/['"<>]/) or break
-
- delim = @scanner.matched
- if delim == "<"
- match = match.chop
- @scanner.pos -= 1
- end
-
- text << match
- break if delim == "<" || delim == ">"
-
- # consume the quoted region
- while match = @scanner.scan_until(/[\\#{delim}]/)
- text << match
- break if @scanner.matched == delim
- break if @scanner.eos?
- text << @scanner.getch # skip the escaped character
- end
- end
- text
- end
- end
-
-end
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/version.rb b/actionview/lib/action_view/vendor/html-scanner/html/version.rb
deleted file mode 100644
index 6d645c3e14..0000000000
--- a/actionview/lib/action_view/vendor/html-scanner/html/version.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module HTML #:nodoc:
- module Version #:nodoc:
-
- MAJOR = 0
- MINOR = 5
- TINY = 3
-
- STRING = [ MAJOR, MINOR, TINY ].join(".")
-
- end
-end
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index 80a41f2418..2e203a7590 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -38,7 +38,11 @@ module ActionView
def handle_deprecated_parent_prefixes # TODO: remove in 4.3/5.0.
return unless respond_to?(:parent_prefixes)
- ActiveSupport::Deprecation.warn "Overriding ActionController::Base::parent_prefixes is deprecated, override .local_prefixes instead."
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Overriding `ActionController::Base::parent_prefixes` is deprecated,
+ override `.local_prefixes` instead.
+ MSG
+
local_prefixes + parent_prefixes
end
end