Spec-Zone.ru › Ruby on Rails 4.1

класс HTML::Selector

Родитель:
Объект

Выбирает элементы HTML с помощью селекторов CSS 2.

Класс Selector использует выражения селекторов CSS для сопоставления и выбора элементов HTML.

Например:

selector = HTML::Selector.new "form.login[action=/login]"

создает новый селектор, который соответствует любому элементу form с классом login и атрибутом action со значением /login.

Сопоставление элементов

Используйте метод match, чтобы определить, соответствует ли элемент селектору.

Для простых селекторов метод возвращает массив с этим элементом или nil если элемент не соответствует. Для сложных селекторов (см. ниже) метод возвращает массив со всеми соответствующими элементами или nil если совпадений не найдено.

Например:

if selector.match(element)
  puts "Element is a login form"
end

Выбор элементов

Используйте метод select для выбора всех соответствующих элементов, начиная с одного элемента и проходя через всех потомков в порядке обхода в глубину.

Этот метод возвращает массив всех соответствующих элементов, пустой массив, если совпадений не найдено.

Например:

selector = HTML::Selector.new "input[type=text]"
matches = selector.select(element)
matches.each do |match|
  puts "Found text field with name #{match.attributes['name']}"
end

Выражения

Селекторы могут сопоставлять элементы по следующим критериям:

  • name – Сопоставление элемента по его имени (имени тега). Например, p для сопоставления абзаца. Вы можете использовать * для сопоставления любого элемента.

  • #id – Сопоставление элемента по его идентификатору (атрибут id). Например, #page.

  • .class – Сопоставление элемента по имени класса, всех имён классов, если указано более одного.

  • [attr] – Сопоставление элемента, имеющего указанный атрибут.

  • [attr=value] – Сопоставление элемента, имеющего указанный атрибут и значение. (Поддерживаются дополнительные операторы, см. ниже).

  • :pseudo-class – Сопоставление элемента на основе псевдокласса, такого как :nth-child и :empty.

  • :not(expr) – Сопоставление элемента, не соответствующего выражению отрицания.

При использовании комбинации вышеперечисленного, имя элемента ставится первым, за ним идентификатор, имена классов, атрибуты, псевдоклассы и отрицания в любом порядке. Не разделяйте эти части пробелами! Пробелы используются для селекторов потомков.

Например:

selector = HTML::Selector.new "form.login[action=/login]"

Сопоставленный элемент должен быть типа form и иметь класс login. Он может иметь другие классы, но класс login необходим для соответствия. Он также должен иметь атрибут под названием action со значением /login.

Этот селектор будет соответствовать следующему элементу:

<form class="login form" method="post" action="/login">

но не будет соответствовать элементу:

<form method="post" action="/logout">

Значения атрибутов

Поддерживаются несколько операторов для сопоставления атрибутов:

  • name – Элемент должен иметь атрибут с указанным именем.

  • name=value – Элемент должен иметь атрибут с указанным именем и значением.

  • name^=value – Значение атрибута должно начинаться с указанного значения.

  • name$=value – Значение атрибута должно заканчиваться указанным значением.

  • name*=value – Значение атрибута должно содержать указанное значение.

  • name~=word – Значение атрибута должно содержать указанное слово (разделенные пробелом).

  • name|=word – Значение атрибута должно начинаться с указанного слова.

Например, следующие два селектора соответствуют одному и тому же элементу:

#my_id
[id=my_id]

и следующие два селектора:

.my_class
[class~=my_class]

Альтернативы, соседи, потомки

Сложные селекторы используют комбинацию выражений для сопоставления элементов:

  • expr1 expr2 – Сопоставление любого элемента со вторым выражением, если он имеет родительский элемент, который соответствует первому выражению.

  • expr1 > expr2 – Сопоставление любого элемента со вторым выражением, если он является потомком элемента, соответствующего первому выражению.

  • expr1 + expr2 – Сопоставление любого элемента со вторым выражением, если он непосредственно следует за элементом, соответствующим первому выражению.

  • expr1 ~ expr2 – Сопоставление любого элемента со вторым выражением, если он следует за элементом, соответствующим первому выражению.

  • expr1, expr2 – Сопоставление любого элемента с первым выражением или со вторым выражением.

Поскольку селекторы потомков и соседей могут соответствовать более чем одному элементу, исходя из первого элемента, метод match может вернуть более одного совпадения.

Псевдоклассы

Псевдоклассы были введены в CSS 3. Они чаще всего используются для выбора элементов в заданном положении:

  • :root – Сопоставление элемента только в том случае, если он является корневым элементом (нет родительского элемента).

  • :empty – Сопоставление элемента только в том случае, если он не имеет дочерних элементов и не содержит текстового контента.

  • :content(string) – Сопоставление элемента только в том случае, если он содержит string в качестве своего текстового содержимого (не учитывая начальные и конечные пробелы).

  • :only-child – Сопоставление элемента, если он является единственным потомком (элементом) родительского элемента.

  • :only-of-type – Сопоставление элемента, если он является единственным потомком (элементом) родительского элемента своего типа.

  • :first-child – Сопоставление элемента, если он является первым потомком (элементом) родительского элемента.

  • :first-of-type – Сопоставление элемента, если он является первым потомком (элементом) родительского элемента своего типа.

  • :last-child – Сопоставление элемента, если он является последним потомком (элементом) родительского элемента.

  • :last-of-type – Сопоставление элемента, если он является последним потомком (элементом) родительского элемента своего типа.

  • :nth-child(b) – Сопоставление элемента, если он является b-м потомком (элементом) родительского элемента. Значение b указывает его индекс, начиная с 1.

  • :nth-child(an+b) – Сопоставление элемента, если он является b-м потомком (элементом) в каждой группе из a потомков элемента родителя.

  • :nth-child(-an+b) – Сопоставление элемента, если он является первым потомком (элементом) в каждой группе из a потомков родительского элемента, до первого b потомков родительского элемента.

  • :nth-child(odd) – Сопоставление элемента с нечётной позицией (т.е. первой, третьей). То же, что и :nth-child(2n+1).

  • :nth-child(even) – Сопоставление элемента с чётной позицией (т.е. второй, четвёртой). То же, что и :nth-child(2n+2).

  • :nth-of-type(..) – Как выше, но учитывает только элементы его типа.

  • :nth-last-child(..) – Как выше, но считает с последнего потомка.

  • :nth-last-of-type(..) – Как выше, но считает с последнего потомка и только элементы его типа.

  • :not(selector) – Сопоставление элемента только в том случае, если элемент не соответствует простому селектору.

Как видите, :nth-child псевдокласс и его варианты могут быть довольно сложными, и спецификация CSS не делает лучшую работу по его объяснению. Но после прочтения примеров и нескольких попыток комбинаций, это легко понять.

Например:

table tr:nth-child(odd)

Выбирает каждую вторую строку в таблице, начиная с первой.

div p:nth-child(4)

Выбирает четвёртый абзац в div, но не если div содержит другие элементы, так как они также считаются.

div p:nth-of-type(4)

Выбирает четвёртый абзац в div, считая только абзацы и игнорируя все остальные элементы.

div p:nth-of-type(-n+4)

Выбирает первые четыре абзаца, игнорируя все остальные.

И вы всегда можете выбрать элемент, соответствующий одному набору правил, но не другому, используя :not. Например:

p:not(.post)

Сопоставляет все абзацы, которые не имеют класса .post.

Замещающие значения

Вы можете использовать замену с идентификаторами, именами классов и значениями элементов. Замена имеет вид знака вопроса (?) и использует следующее значение в списке аргументов после выражения CSS.

Значение замены может быть строкой или регулярным выражением. Все остальные значения преобразуются в строки.

Например:

selector = HTML::Selector.new "#?", /^\d+$/

соответствует любому элементу, чьей идентификатор состоит из одной или нескольких цифр.

См. www.w3.org/TR/css3-selectors/

END_OF_DOCUMENT_MARKER

Методы публичного класса

for_class(cls) → selector Показать исходный код

Создаёт новый селектор для заданного имени класса.

# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 216
def for_class(cls)
  self.new([".?", cls])
end
for_id(id) → selector Показать исходный код

Создаёт новый селектор для заданного идентификатора.

# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 225
def for_id(id)
  self.new(["#?", id])
end
new(string, [values ...]) → selector Показать исходный код

Создаёт новый селектор из выражения CSS 2 селектора.

Первый аргумент — выражение селектора. Все последующие аргументы используются для подстановки значений.

Выбрасывает InvalidSelectorError, если выражение селектора некорректно.

# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 241
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

Методы публичного экземпляра

match(element, first?) → array or nil Показать исходный код

Сопоставляет элемент с селектором.

Для простого селектора этот метод возвращает массив с элементом, если он соответствует, и nil в противном случае.

Для сложного селектора (свойствами "sibling" и "descendant") этот метод возвращает массив со всеми соответствующими элементами, а nil, если совпадений не найдено.

Используйте +first_only=true+, если вам нужен только первый элемент.

Например:

if selector.match(element)
  puts "Element is a login form"
end
# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 365
def match(element, first_only = false)
  # Match element if no element name or element name same as element name
  if matched = (!@tag_name || @tag_name == element.name)
    # No match if one of the attribute matches failed
    for attr in @attributes
      if element.attributes[attr[0]] !~ attr[1]
        matched = false
        break
      end
    end
  end

  # Pseudo class matches (nth-child, empty, etc).
  if matched
    for pseudo in @pseudo
      unless pseudo.call(element)
        matched = false
        break
      end
    end
  end

  # Negation. Same rules as above, but we fail if a match is made.
  if matched && @negation
    for negation in @negation
      if negation[:tag_name] == element.name
        matched = false
      else
        for attr in negation[:attributes]
          if element.attributes[attr[0]] =~ attr[1]
            matched = false
            break
          end
        end
      end
      if matched
        for pseudo in negation[:pseudo]
          if pseudo.call(element)
            matched = false
            break
          end
        end
      end
      break unless matched
    end
  end

  # If element matched but depends on another element (child,
  # sibling, etc), apply the dependent matches instead.
  if matched && @depends
    matches = @depends.call(element, first_only)
  else
    matches = matched ? [element] : nil
  end

  # If this selector is part of the group, try all the alternative
  # selectors (unless first_only).
  if !first_only || !matches
    @alternates.each do |alternate|
      break if matches && first_only
      if subset = alternate.match(element, first_only)
        if matches
          matches.concat subset
        else
          matches = subset
        end
      end
    end
  end

  matches
end
next_element(element, name = nil) Показать исходный код

Возвращает следующий элемент после текущего. Пропускает текстовые узлы-сиблинги.

С аргументом name, возвращает следующий элемент с этим именем, пропуская другие элементы-сиблинги.

# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 495
def next_element(element, name = nil)
  if siblings = element.parent.children
    found = false
    siblings.each do |node|
      if node.equal?(element)
        found = true
      elsif found && node.tag?
        return node if (name.nil? || node.name == name)
      end
    end
  end
  nil
end
select(root) → array Показать исходный код

Выбирает и возвращает массив со всеми совпадающими элементами, начиная с одного узла и переходя через всех детей по принципу "depth-first". Возвращает пустой массив, если совпадений не найдено.

Узел root может быть любым элементом документа или самим документом.

Например:

selector = HTML::Selector.new "input[type=text]"
matches = selector.select(element)
matches.each do |match|
  puts "Found text field with name #{match.attributes['name']}"
end
# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 455
def select(root)
  matches = []
  stack = [root]
  while node = stack.pop
    if node.tag? && subset = match(node, false)
      subset.each do |match|
        matches << match unless matches.any? { |item| item.equal?(match) }
      end
    elsif children = node.children
      stack.concat children.reverse
    end
  end
  matches
end
select_first(root) Показать исходный код

Аналогично select, но возвращает первый совпадающий элемент. Возвращает nil если ни один элемент не соответствует селектору.

# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 473
def select_first(root)
  stack = [root]
  while node = stack.pop
    if node.tag? && subset = match(node, true)
      return subset.first if !subset.empty?
    elsif children = node.children
      stack.concat children.reverse
    end
  end
  nil
end

Защищённые методы экземпляра

attribute_match(equality, value) Показать исходный код

Создаёт регулярное выражение для сопоставления значения атрибута, основываясь на операторе равенства (=, ^=, |=, и т.д.).

# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 689
def attribute_match(equality, value)
  regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
  case equality
    when "=" then
      # Match the attribute value in full
      Regexp.new("^#{regexp}$")
    when "~=" then
      # Match a space-separated word within the attribute value
      Regexp.new("(^|\s)#{regexp}($|\s)")
    when "^="
      # Match the beginning of the attribute value
      Regexp.new("^#{regexp}")
    when "$="
      # Match the end of the attribute value
      Regexp.new("#{regexp}$")
    when "*="
      # Match substring of the attribute value
      regexp.is_a?(Regexp) ? regexp : Regexp.new(regexp)
    when "|=" then
      # Match the first space-separated item of the attribute value
      Regexp.new("^#{regexp}($|\s)")
    else
      raise InvalidSelectorError, "Invalid operation/value" unless value.empty?
      # Match all attributes values (existence check)
      //
  end
end
next_selector(statement, values) Показать исходный код

Вызывается для создания зависимого селектора (sibling, descendant и т.д.). Передаёт остаток от выражения, который в итоге будет сведён к нулю, и массив значений для подстановки.

Этот метод вызывается в четырёх местах, поэтому полезно его расположить здесь для повторного использования. Единственная логика связана с необходимостью распознавания разделителей запятыми (альтернативные) и их применением к группе селекторов верхнего селектора.

# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 802
def next_selector(statement, values)
  second = Selector.new(statement, values)
  # 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
  second
end
nth_child(a, b, of_type, reverse) Показать исходный код

Возвращает лямбда-функцию, которая может сопоставлять элемент с псевдоклассом nth-child, учитывая следующие аргументы:

  • a – Значение части а.

  • b – Значение части b.

  • of_type – Истина, если проверять только элементы данного типа (of-type).

  • reverse – Истина, если считать в обратном порядке (last-).

# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 724
def nth_child(a, b, of_type, reverse)
  # a = 0 means select at index b, if b = 0 nothing selected
  return lambda { |element| false } if a == 0 && b == 0
  # a < 0 and b < 0 will never match against an index
  return lambda { |element| false } if a < 0 && b < 0
  b = a + b + 1 if b < 0   # b < 0 just picks last element from each group
  b -= 1 unless b == 0  # b == 0 is same as b == 1, otherwise zero based
  lambda do |element|
    # Element must be inside parent element.
    return false unless element.parent && element.parent.tag?
    index = 0
    # Get siblings, reverse if counting from last.
    siblings = element.parent.children
    siblings = siblings.reverse if reverse
    # Match element name if of-type, otherwise ignore name.
    name = of_type ? element.name : nil
    found = false
    for child in siblings
      # Skip text nodes/comments.
      if child.tag? && (name == nil || child.name == name)
        if a == 0
          # Shortcut when a == 0 no need to go past count
          if index == b
            found = child.equal?(element)
            break
          end
        elsif a < 0
          # Only look for first b elements
          break if index > b
          if child.equal?(element)
            found = (index % a) == 0
            break
          end
        else
          # Otherwise, break if child found and count ==  an+b
          if child.equal?(element)
            found = (index % a) == b
            break
          end
        end
        index += 1
      end
    end
    found
  end
end
only_child(of_type) Показать исходный код

Создаёт лямбда-функцию для проверки единственного ребёнка. Передайте of-type чтобы рассматривать только элементы данного типа.

# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 774
def only_child(of_type)
  lambda do |element|
    # Element must be inside parent element.
    return false unless element.parent && element.parent.tag?
    name = of_type ? element.name : nil
    other = false
    for child in element.parent.children
      # Skip text nodes/comments.
      if child.tag? && (name == nil || child.name == name)
        unless child.equal?(element)
          other = true
          break
        end
      end
    end
    !other
  end
end
simple_selector(statement, values, can_negate = true) Показать исходный код

Создаёт простой селектор, используя выражение и массив значений для подстановки.

Возвращает хэш со значениями tag_name, attributes, pseudo (классы) и negation.

Вызывается первый раз со значением can_negate true, чтобы разрешить отрицание. Вызывается второй раз со значением false, так как отрицание не может быть отрицаемым.

# File actionview/lib/action_view/vendor/html-scanner/html/selector.rb, line 522
def simple_selector(statement, values, can_negate = true)
  tag_name = nil
  attributes = []
  pseudo = []
  negation = []

  # Element name. (Note that in negation, this can come at
  # any order, but for simplicity we allow if only first).
  statement.sub!(/^(\*|[[:alpha:]][\w\-]*)/) do |match|
    match.strip!
    tag_name = match.downcase unless match == "*"
    @source << match
    "" # Remove
  end

  # Get identifier, class, attribute name, pseudo or negation.
  while true
    # Element identifier.
    next if statement.sub!(/^#(\?|[\w\-]+)/) do
      id = $1
      if id == "?"
        id = values.shift
      end
      @source << "##{id}"
      id = Regexp.new("^#{Regexp.escape(id.to_s)}$") unless id.is_a?(Regexp)
      attributes << ["id", id]
      "" # Remove
    end

    # Class name.
    next if statement.sub!(/^\.([\w\-]+)/) do
      class_name = $1
      @source << ".#{class_name}"
      class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp)
      attributes << ["class", class_name]
      "" # Remove
    end

    # Attribute value.
    next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do
      name, equality, value = $1, $2, $3
      if value == "?"
        value = values.shift
      else
        # Handle single and double quotes.
        value.strip!
        if (value[0] == ?" || value[0] == ?') && value[0] == value[-1]
          value = value[1..-2]
        end
      end
      @source << "[#{name}#{equality}'#{value}']"
      attributes << [name.downcase.strip, attribute_match(equality, value)]
      "" # Remove
    end

    # Root element only.
    next if statement.sub!(/^:root/) do
      pseudo << lambda do |element|
        element.parent.nil? || !element.parent.tag?
      end
      @source << ":root"
      "" # Remove
    end

    # Nth-child including last and of-type.
    next if statement.sub!(/^:nth-(last-)?(child|of-type)\((odd|even|(\d+|\?)|(-?\d*|\?)?n([+\-]\d+|\?)?)\)/) do |match|
      reverse = $1 == "last-"
      of_type = $2 == "of-type"
      @source << ":nth-#{$1}#{$2}("
      case $3
        when "odd"
          pseudo << nth_child(2, 1, of_type, reverse)
          @source << "odd)"
        when "even"
          pseudo << nth_child(2, 2, of_type, reverse)
          @source << "even)"
        when /^(\d+|\?)$/  # b only
          b = ($1 == "?" ? values.shift : $1).to_i
          pseudo << nth_child(0, b, of_type, reverse)
          @source << "#{b})"
        when /^(-?\d*|\?)?n([+\-]\d+|\?)?$/
          a = ($1 == "?" ? values.shift :
               $1 == "" ? 1 : $1 == "-" ? -1 : $1).to_i
          b = ($2 == "?" ? values.shift : $2).to_i
          pseudo << nth_child(a, b, of_type, reverse)
          @source << (b >= 0 ? "#{a}n+#{b})" : "#{a}n#{b})")
        else
          raise ArgumentError, "Invalid nth-child #{match}"
      end
      "" # Remove
    end
    # First/last child (of type).
    next if statement.sub!(/^:(first|last)-(child|of-type)/) do
      reverse = $1 == "last"
      of_type = $2 == "of-type"
      pseudo << nth_child(0, 1, of_type, reverse)
      @source << ":#{$1}-#{$2}"
      "" # Remove
    end
    # Only child (of type).
    next if statement.sub!(/^:only-(child|of-type)/) do
      of_type = $1 == "of-type"
      pseudo << only_child(of_type)
      @source << ":only-#{$1}"
      "" # Remove
    end

    # Empty: no child elements or meaningful content (whitespaces
    # are ignored).
    next if statement.sub!(/^:empty/) do
      pseudo << lambda do |element|
        empty = true
        for child in element.children
          if child.tag? || !child.content.strip.empty?
            empty = false
            break
          end
        end
        empty
      end
      @source << ":empty"
      "" # Remove
    end
    # Content: match the text content of the element, stripping
    # leading and trailing spaces.
    next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do
      content = $1
      if content == "?"
        content = values.shift
      elsif (content[0] == ?" || content[0] == ?') && content[0] == content[-1]
        content = content[1..-2]
      end
      @source << ":content('#{content}')"
      content = Regexp.new("^#{Regexp.escape(content.to_s)}$") unless content.is_a?(Regexp)
      pseudo << lambda do |element|
        text = ""
        for child in element.children
          unless child.tag?
            text << child.content
          end
        end
        text.strip =~ content
      end
      "" # Remove
    end

    # Negation. Create another simple selector to handle it.
    if statement.sub!(/^:not\(\s*/, "")
      raise ArgumentError, "Double negatives are not missing feature" unless can_negate
      @source << ":not("
      negation << simple_selector(statement, values, false)
      raise ArgumentError, "Negation not closed" unless statement.sub!(/^\s*\)/, "")
      @source << ")"
      next
    end

    # No match: moving on.
    break
  end

  # Return hash. The keys are mapped to instance variables.
  {:tag_name=>tag_name, :attributes=>attributes, :pseudo=>pseudo, :negation=>negation}
end

© 2004–2016 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API