# frozen_string_literal: true require 'haml/attribute_builder' require 'haml/attribute_parser' require 'haml/ruby_expression' module Haml class AttributeCompiler def initialize(identity, options) @identity = identity @quote = options[:attr_quote] @format = options[:format] @escape_attrs = options[:escape_attrs] end def compile(node) hashes = [] if node.value[:object_ref] != :nil || !AttributeParser.available? return runtime_compile(node) end [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str| hash = AttributeParser.parse(attribute_str) return runtime_compile(node) unless hash hashes << hash end static_compile(node.value[:attributes], hashes) end private def runtime_compile(node) attrs = [] attrs.unshift(node.value[:attributes].inspect) if node.value[:attributes] != {} args = [ @escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect, '::Haml::AttributeBuilder::BOOLEAN_ATTRIBUTES', node.value[:object_ref], ] + attrs [:html, :attrs, [:dynamic, "::Haml::AttributeBuilder.build(#{args.join(', ')}, #{node.value[:dynamic_attributes].to_literal})"]] end def static_compile(static_hash, dynamic_hashes) temple = [:html, :attrs] keys = [*static_hash.keys, *dynamic_hashes.map(&:keys).flatten].uniq.sort keys.each do |key| values = [[:static, static_hash[key]], *dynamic_hashes.map { |h| [:dynamic, h[key]] }] values.select! { |_, exp| exp != nil } case key when 'id' compile_id!(temple, key, values) when 'class' compile_class!(temple, key, values) when 'data', 'aria' compile_data!(temple, key, values) when *AttributeBuilder::BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/ compile_boolean!(temple, key, values) else compile_common!(temple, key, values) end end temple end def compile_id!(temple, key, values) build_code = attribute_builder(:id, values) if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) } temple << [:html, :attr, key, [:static, eval(build_code).to_s]] else temple << [:html, :attr, key, [:dynamic, build_code]] end end def compile_class!(temple, key, values) build_code = attribute_builder(:class, values) if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) } temple << [:html, :attr, key, [:static, eval(build_code).to_s]] else temple << [:html, :attr, key, [:dynamic, build_code]] end end def compile_data!(temple, key, values) args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", values.map { |v| literal_for(v) }] build_code = "::Haml::AttributeBuilder.build_#{key}(#{args.join(', ')})" if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) } temple << [:static, eval(build_code).to_s] else temple << [:dynamic, build_code] end end def compile_boolean!(temple, key, values) exp = literal_for(values.last) if Temple::StaticAnalyzer.static?(exp) value = eval(exp) case value when true then temple << [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]] when false, nil else temple << [:html, :attr, key, [:fescape, @escape_attrs, [:static, value.to_s]]] end else var = @identity.generate temple << [ :case, "(#{var} = (#{exp}))", ['true', [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]], ['false, nil', [:multi]], [:else, [:multi, [:static, " #{key}=#{@quote}"], [:fescape, @escape_attrs, [:dynamic, var]], [:static, @quote]]], ] end end def compile_common!(temple, key, values) temple << [:html, :attr, key, [:fescape, @escape_attrs, values.last]] end def attribute_builder(type, values) args = [@escape_attrs.inspect, *values.map { |v| literal_for(v) }] "::Haml::AttributeBuilder.build_#{type}(#{args.join(', ')})" end def literal_for(value) type, exp = value type == :static ? "#{exp.inspect}.freeze" : exp end end end