159 lines
3.8 KiB
Ruby
159 lines
3.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "addressable/uri"
|
|
|
|
module HTMLProofer
|
|
# Represents the element currently being processed
|
|
class Element
|
|
include HTMLProofer::Utils
|
|
|
|
attr_reader :node, :url, :base_url, :line, :content
|
|
|
|
def initialize(runner, node, base_url: nil)
|
|
@runner = runner
|
|
@node = node
|
|
|
|
swap_attributes!
|
|
|
|
@base_url = base_url
|
|
@url = Attribute::Url.new(runner, link_attribute, base_url: base_url, source: @runner.current_source, filename: @runner.current_filename)
|
|
|
|
@line = node.line
|
|
@content = node.content
|
|
end
|
|
|
|
def link_attribute
|
|
meta_content || src || srcset || href
|
|
end
|
|
|
|
def meta_content
|
|
return unless meta_tag?
|
|
|
|
@node["content"]
|
|
end
|
|
|
|
def meta_tag?
|
|
@node.name == "meta"
|
|
end
|
|
|
|
def src
|
|
return if !img_tag? && !script_tag? && !source_tag?
|
|
|
|
@node["src"]
|
|
end
|
|
|
|
def img_tag?
|
|
@node.name == "img"
|
|
end
|
|
|
|
def script_tag?
|
|
@node.name == "script"
|
|
end
|
|
|
|
def srcset
|
|
return if !img_tag? && !source_tag?
|
|
|
|
@node["srcset"]
|
|
end
|
|
|
|
def source_tag?
|
|
@node.name == "source"
|
|
end
|
|
|
|
def href
|
|
return if !a_tag? && !link_tag?
|
|
|
|
@node["href"]
|
|
end
|
|
|
|
def a_tag?
|
|
@node.name == "a"
|
|
end
|
|
|
|
def link_tag?
|
|
@node.name == "link"
|
|
end
|
|
|
|
def aria_hidden?
|
|
@node.attributes["aria-hidden"]&.value == "true"
|
|
end
|
|
|
|
def multiple_srcsets?
|
|
!blank?(srcset) && srcset.split(",").size > 1
|
|
end
|
|
|
|
# From https://github.com/sindresorhus/srcset/blob/f7c48acd7facf18e94dec47e6b96e84e0f0e69dc/index.js#LL1-L16C71
|
|
# This regex represents a loose rule of an “image candidate string”; see https://html.spec.whatwg.org/multipage/images.html#srcset-attribute
|
|
# An “image candidate string” roughly consists of the following:
|
|
# 1. Zero or more whitespace characters.
|
|
# 2. A non-empty URL that does not start or end with `,`.
|
|
# 3. Zero or more whitespace characters.
|
|
# 4. An optional “descriptor” that starts with a whitespace character.
|
|
# 5. Zero or more whitespace characters.
|
|
# 6. Each image candidate string is separated by a `,`.
|
|
# We intentionally implement a loose rule here so that we can perform more aggressive error handling and reporting in the below code.
|
|
|
|
IMAGE_CANDIDATE_REGEX = /\s*([^,]\S*[^,](?:\s+[^,]+)?)\s*(?:,|$)/
|
|
|
|
def srcsets
|
|
return if blank?(srcset)
|
|
|
|
srcset.split(IMAGE_CANDIDATE_REGEX).select.with_index do |_part, idx|
|
|
idx.odd?
|
|
end.map(&:strip)
|
|
end
|
|
|
|
def multiple_sizes?
|
|
return false if blank?(srcsets)
|
|
|
|
srcsets.any? do |srcset|
|
|
!blank?(srcset) && srcset.split(" ").size > 1
|
|
end
|
|
end
|
|
|
|
def srcsets_wo_sizes
|
|
return if blank?(srcsets)
|
|
|
|
srcsets.map do |srcset|
|
|
srcset.split(" ").first
|
|
end
|
|
end
|
|
|
|
def ignore?
|
|
return true if @node.attributes["data-proofer-ignore"]
|
|
return true if ancestors_ignorable?
|
|
|
|
return true if url&.ignore?
|
|
|
|
false
|
|
end
|
|
|
|
private def attribute_swapped?
|
|
return false if blank?(@runner.options[:swap_attributes])
|
|
|
|
attrs = @runner.options[:swap_attributes][@node.name]
|
|
|
|
true unless blank?(attrs)
|
|
end
|
|
|
|
private def swap_attributes!
|
|
return unless attribute_swapped?
|
|
|
|
attr_swaps = @runner.options[:swap_attributes][@node.name]
|
|
|
|
attr_swaps.flatten.each_slice(2) do |(old_attr, new_attr)|
|
|
next if blank?(node[old_attr])
|
|
|
|
node[new_attr] = node[old_attr]
|
|
node.delete(old_attr)
|
|
end
|
|
end
|
|
|
|
private def ancestors_ignorable?
|
|
ancestors_attributes = @node.ancestors.map { |a| a.respond_to?(:attributes) && a.attributes }
|
|
ancestors_attributes.pop # remove document at the end
|
|
ancestors_attributes.any? { |a| !a["data-proofer-ignore"].nil? }
|
|
end
|
|
end
|
|
end
|