64 lines
2 KiB
Ruby
64 lines
2 KiB
Ruby
module Banzai
|
|
module Querying
|
|
module_function
|
|
|
|
# Searches a Nokogiri document using a CSS query, optionally optimizing it
|
|
# whenever possible.
|
|
#
|
|
# document - A document/element to search.
|
|
# query - The CSS query to use.
|
|
# reference_options - A hash with nodes filter options
|
|
#
|
|
# Returns an array of Nokogiri::XML::Element objects if location is specified
|
|
# in reference_options. Otherwise it would a Nokogiri::XML::NodeSet.
|
|
def css(document, query, reference_options = {})
|
|
# When using "a.foo" Nokogiri compiles this to "//a[...]" but
|
|
# "descendant::a[...]" is quite a bit faster and achieves the same result.
|
|
xpath = Nokogiri::CSS.xpath_for(query)[0].gsub(%r{^//}, 'descendant::')
|
|
xpath = restrict_to_p_nodes_at_root(xpath) if filter_nodes_at_beginning?(reference_options)
|
|
nodes = document.xpath(xpath)
|
|
|
|
filter_nodes(nodes, reference_options)
|
|
end
|
|
|
|
def restrict_to_p_nodes_at_root(xpath)
|
|
xpath.gsub('descendant::', './p/')
|
|
end
|
|
|
|
def filter_nodes(nodes, reference_options)
|
|
if filter_nodes_at_beginning?(reference_options)
|
|
filter_nodes_at_beginning(nodes)
|
|
else
|
|
nodes
|
|
end
|
|
end
|
|
|
|
def filter_nodes_at_beginning?(reference_options)
|
|
reference_options && reference_options[:location] == :beginning
|
|
end
|
|
|
|
# Selects child nodes if they are present in the beginning among other siblings.
|
|
#
|
|
# nodes - A Nokogiri::XML::NodeSet.
|
|
#
|
|
# Returns an array of Nokogiri::XML::Element objects.
|
|
def filter_nodes_at_beginning(nodes)
|
|
parents_and_nodes = nodes.group_by(&:parent)
|
|
filtered_nodes = []
|
|
|
|
parents_and_nodes.each do |parent, nodes|
|
|
children = parent.children
|
|
nodes = nodes.to_a
|
|
|
|
children.each do |child|
|
|
next if child.text.blank?
|
|
node = nodes.shift
|
|
break unless node == child
|
|
filtered_nodes << node
|
|
end
|
|
end
|
|
|
|
filtered_nodes
|
|
end
|
|
end
|
|
end
|