2011-01-30 2 views
10

Видимо метод Nokogiri в add_class работает только на NodeList с, что делает этот код недействителен:Добавьте класс к элементу с Nokogiri

doc.search('a').each do |anchor| 
    anchor.inner_text = "hello!" 
    anchor.add_class("whatever") # WHOOPS! 
end 

Что я могу сделать, чтобы сделать этот код работать? Я подумал, что это будет что-то вроде

doc.search('a').each do |anchor| 
    anchor.inner_text = "hello!" 
    Nokogiri::XML::NodeSet.new(anchor).add_class("whatever") 
end 

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

ответ

12

Класс CSS это просто еще один атрибут элемента:

doc.search('a').each do |anchor| 
    anchor.inner_text = "hello!" 
    anchor['class']="whatever" 
end 

Поскольку классы CSS являются разделенных пробелами в атрибуте, если вы не уверены, если один или несколько классов, возможно, уже существует, вы будете нужно что-то вроде

anchor['class'] ||= "" 
anchor['class'] = anchor['class'] << " whatever" 

Вам нужно явно задать атрибут, используя = вместо того, чтобы просто мутируют возвращаемую строку для атрибута. Это, к примеру, не будет изменять DOM:

anchor['class'] ||= "" 
anchor['class'] << " whatever" 

Несмотря на то, что приводит к более работы делается, я бы, наверное, сделать это так:

class Nokogiri::XML::Node 
    def add_css_class(*classes) 
    existing = (self['class'] || "").split(/\s+/) 
    self['class'] = existing.concat(classes).uniq.join(" ") 
    end 
end 

Если вы не хотите обезьяне-патч класс, вы могли бы в качестве альтернативы:

module ClassMutator 
    def add_css_class(*classes) 
    existing = (self['class'] || "").split(/\s+/) 
    self['class'] = existing.concat(classes).uniq.join(" ") 
    end 
end 

anchor.extend ClassMutator 
anchor.add_css_class "whatever" 

Edit: вы можете видеть, что это в основном то, что делает Nokogiri внутренне для add_class метод, который вы нашли, нажав на классе, чтобы просмотреть исходный код:

# File lib/nokogiri/xml/node_set.rb, line 136 
def add_class name 
    each do |el| 
    next unless el.respond_to? :get_attribute 
    classes = el.get_attribute('class').to_s.split(" ") 
    el.set_attribute('class', classes.push(name).uniq.join(" ")) 
    end 
    self 
end 
+0

bigup the ClassMutator! Спасибо! – flunder

1

Nokogiri-х add_class, работает на NodeSet, как вы нашли. Попытка добавления класса внутри блока each работать не будет, потому что в этот момент вы работаете над отдельным узлом.

Вместо:

require 'nokogiri' 

html = '<p>one</p><p>two</p>' 
doc = Nokogiri::HTML(html) 

doc.search('p').tap{ |ns| ns.add_class('boo') }.each do |n| 
    puts n.text 
end 
puts doc.to_html 

Какие выходы:

# >> one 
# >> two 
# >> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> 
# >> <html><body> 
# >> <p class="boo">one</p> 
# >> <p class="boo">two</p> 
# >> </body></html> 

tap метод, реализованный в Ruby, 1.9+, дает доступ к самой нодлисте, что позволяет метод add_class добавить "бух" класс к тегам <p>.

+2

Почему вы не можете просто выполнить doc.search ('p'). Add_class ('boo'). Each do ... ' –

+1

Вы можете использовать' doc = Nokogiri :: HTML.fragment (html) ', если вы не хочу, чтобы Nokogiri добавлял doctype и другие теги html и body – Archonic

Смежные вопросы