Add a class to an element with Nokogiri

Nokogiri's add_class, works on a NodeSet, like you found. Trying to add the class inside the each block wouldn't work though, because at that point you are working on an individual node.

Instead:

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

Which outputs:

# >> 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>

The tap method, implemented in Ruby 1.9+, gives access to the nodelist itself, allowing the add_class method to add the "boo" class to the <p> tags.


A CSS class is just another attribute on an element:

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

Since CSS classes are space-delimited in the attribute, if you're not sure if one or more classes might already exist you'll need something like

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

You need to explicitly set the attribute using = instead of just mutating the string returned for the attribute. This, for example, will not change the DOM:

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

Even though it results in more work being done, I'd probably do this like so:

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

If you don't want to monkey-patch the class, you could alternatively:

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: You can see that this is basically what Nokogiri does internally for the add_class method you found by clicking on the class to view the source:

# 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

Tags:

Ruby

Nokogiri