class HTML::Selector
def initialize(selector, *values)
are used for value substitution.
The first argument is the selector expression. All other arguments
Creates a new selector from a CSS 2 selector expression.
Selector.new(string, [values ...]) => selector
:call-seq:
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