# frozen_string_literal: true require 'haml/object_ref' module Haml::AttributeBuilder BOOLEAN_ATTRIBUTES = %w[disabled readonly multiple checked autobuffer autoplay controls loop selected hidden scoped async defer reversed ismap seamless muted required autofocus novalidate formnovalidate open pubdate itemscope allowfullscreen default inert sortable truespeed typemustmatch download].freeze # For JRuby, TruffleRuby, and Wasm, fallback to Ruby implementation. if /java|wasm/ === RUBY_PLATFORM || RUBY_ENGINE == 'truffleruby' class << self def build(escape_attrs, quote, format, boolean_attributes, object_ref, *hashes) hashes << Haml::ObjectRef.parse(object_ref) if object_ref buf = [] hash = merge_all_attrs(hashes) keys = hash.keys.sort! keys.each do |key| case key when 'id'.freeze buf << " id=#{quote}#{build_id(escape_attrs, *hash[key])}#{quote}" when 'class'.freeze buf << " class=#{quote}#{build_class(escape_attrs, *hash[key])}#{quote}" when 'data'.freeze buf << build_data(escape_attrs, quote, *hash[key]) when *boolean_attributes, /\Adata-/ build_boolean!(escape_attrs, quote, format, buf, key, hash[key]) else buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}" end end buf.join end def build_id(escape_attrs, *values) escape_html(escape_attrs, values.flatten.select { |v| v }.join('_')) end def build_class(escape_attrs, *values) if values.size == 1 value = values.first case when value.is_a?(String) # noop when value.is_a?(Array) value = value.flatten.select { |v| v }.map(&:to_s).uniq.join(' ') when value value = value.to_s else return '' end return escape_html(escape_attrs, value) end classes = [] values.each do |value| case when value.is_a?(String) classes += value.split(' ') when value.is_a?(Array) classes += value.select { |v| v } when value classes << value.to_s end end escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' ')) end def build_data(escape_attrs, quote, *hashes) build_data_attribute(:data, escape_attrs, quote, *hashes) end def build_aria(escape_attrs, quote, *hashes) build_data_attribute(:aria, escape_attrs, quote, *hashes) end private def build_data_attribute(key, escape_attrs, quote, *hashes) attrs = [] if hashes.size > 1 && hashes.all? { |h| h.is_a?(Hash) } data_value = merge_all_attrs(hashes) else data_value = hashes.last end hash = flatten_attributes(key => data_value) hash.sort_by(&:first).each do |key, value| case value when true attrs << " #{key}" when nil, false # noop else attrs << " #{key}=#{quote}#{escape_html(escape_attrs, value.to_s)}#{quote}" end end attrs.join end def flatten_attributes(attributes) flattened = {} attributes.each do |key, value| case value when attributes when Hash flatten_attributes(value).each do |k, v| if k.nil? flattened[key] = v else flattened["#{key}-#{k.to_s.gsub(/_/, '-')}"] = v end end else flattened[key] = value if value end end flattened end def merge_all_attrs(hashes) merged = {} hashes.each do |hash| hash.each do |key, value| key = key.to_s case key when 'id'.freeze, 'class'.freeze, 'data'.freeze merged[key] ||= [] merged[key] << value else merged[key] = value end end end merged end def build_boolean!(escape_attrs, quote, format, buf, key, value) case value when true case format when :xhtml buf << " #{key}=#{quote}#{key}#{quote}" else buf << " #{key}" end when false, nil # omitted else buf << " #{key}=#{quote}#{escape_html(escape_attrs, value)}#{quote}" end end def escape_html(escape_attrs, str) if escape_attrs Haml::Util.escape_html(str) else str end end end else # Haml::AttributeBuilder.build # Haml::AttributeBuilder.build_id # Haml::AttributeBuilder.build_class # Haml::AttributeBuilder.build_data # Haml::AttributeBuilder.build_aria require 'haml/haml' end end