public Method

Tag.match(conditions)

Returns true if the node meets any of the given conditions. The conditions parameter must be a hash of any of the following keys (all are optional):

  • :tag: the node name must match the corresponding value
  • :attributes: a hash. The node’s values must match the corresponding values in the hash.
  • :parent: a hash. The node’s parent must match the corresponding hash.
  • :child: a hash. At least one of the node’s immediate children must meet the criteria described by the hash.
  • :ancestor: a hash. At least one of the node’s ancestors must meet the criteria described by the hash.
  • :descendant: a hash. At least one of the node’s descendants must meet the criteria described by the hash.
  • :sibling: a hash. At least one of the node’s siblings must meet the criteria described by the hash.
  • :after: a hash. The node must be after any sibling meeting the criteria described by the hash, and at least one sibling must match.
  • :before: a hash. The node must be before any sibling meeting the criteria described by the hash, and at least one sibling must match.
  • :children: a hash, for counting children of a node. Accepts the keys:

** :count: either a number or a range which must equal (or

include) the number of children that match.

** :less_than: the number of matching children must be less than

this number.

** :greater_than: the number of matching children must be

greater than this number.

** :only: another hash consisting of the keys to use

to match on the children, and only matching children will be
counted.

Conditions are matched using the following algorithm:

  • if the condition is a string, it must be a substring of the value.
  • if the condition is a regexp, it must match the value.
  • if the condition is a number, the value must match number.to_s.
  • if the condition is true, the value must not be nil.
  • if the condition is false or nil, the value must be nil.

Usage:

# test if the node is a "span" tag
node.match :tag => "span"

# test if the node's parent is a "div"
node.match :parent => { :tag => "div" }

# test if any of the node's ancestors are "table" tags
node.match :ancestor => { :tag => "table" }

# test if any of the node's immediate children are "em" tags
node.match :child => { :tag => "em" }

# test if any of the node's descendants are "strong" tags
node.match :descendant => { :tag => "strong" }

# test if the node has between 2 and 4 span tags as immediate children
node.match :children => { :count => 2..4, :only => { :tag => "span" } }

# get funky: test to see if the node is a "div", has a "ul" ancestor
# and an "li" parent (with "class" = "enum"), and whether or not it has
# a "span" descendant that contains # text matching /hello world/:
node.match :tag => "div",
           :ancestor => { :tag => "ul" },
           :parent => { :tag => "li",
                        :attributes => { :class => "enum" } },
           :descendant => { :tag => "span",
                            :child => /hello world/ }

Source Code

# File action_controller/vendor/html-scanner/html/node.rb, line 410
def match(conditions)
  conditions = validate_conditions(conditions)
  # check content of child nodes
  if conditions[:content]
    if children.empty?
      return false unless match_condition("", conditions[:content])
    else
      return false unless children.find { |child| child.match(conditions[:content]) }
    end
  end

  # test the name
  return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]

  # test attributes
  (conditions[:attributes] || {}).each do |key, value|
    return false unless match_condition(self[key], value)
  end

  # test parent
  return false unless parent.match(conditions[:parent]) if conditions[:parent]

  # test children
  return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child]

  # test ancestors
  if conditions[:ancestor]
    return false unless catch :found do
      p = self
      throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
    end
  end

  # test descendants
  if conditions[:descendant]
    return false unless children.find do |child|
      # test the child
      child.match(conditions[:descendant]) ||
      # test the child's descendants
      child.match(:descendant => conditions[:descendant])
    end
  end

  # count children
  if opts = conditions[:children]
    matches = children.select do |c|
      (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
    end

    matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
    opts.each do |key, value|
      next if key == :only
      case key
        when :count
          if Integer === value
            return false if matches.length != value
          else
            return false unless value.include?(matches.length)
          end
        when :less_than
          return false unless matches.length < value
        when :greater_than
          return false unless matches.length > value
        else raise "unknown count condition #{key}"
      end
    end
  end

  # test siblings
  if conditions[:sibling] || conditions[:before] || conditions[:after]
    siblings = parent ? parent.children : []
    self_index = siblings.index(self)

    if conditions[:sibling]
      return false unless siblings.detect do |s| 
        s != self && s.match(conditions[:sibling])
      end
    end

    if conditions[:before]
      return false unless siblings[self_index+1..-1].detect do |s| 
        s != self && s.match(conditions[:before])
      end
    end

    if conditions[:after]
      return false unless siblings[0,self_index].detect do |s| 
        s != self && s.match(conditions[:after])
      end
    end
  end

  true
end
Comments

Have your say
Please use Textile formatting (click here for a cheat sheet). Use <code/> and <pre/> for code samples.
Click here to login with OpenID to to post comments.