Dave
Dave

Reputation: 19150

How do I write a CSS selector that looks for an element starting with text in a case-insensitive way?

I'm using Rails 5.0.1 with Nokogiri. How do I select a CSS element whose text starts with a certain string in a case insensitive way? Right now I can search for something in a case-sensitive way using

doc.css("#select_id option:starts-with('ABC')")

but I would like to know how to disregard case when looking for an option that starts with certain text?

Upvotes: 2

Views: 1017

Answers (1)

Phrogz
Phrogz

Reputation: 303224

Summary It's ugly. You're better off just using Ruby:

doc.css('select#select_id > option').select{ |opt| opt.text =~ /^ABC/i }

Details
Nokogiri uses libxml2, which uses XPath to search XML and HTML documents. Nokogiri transforms ~CSS expressions into XPath. For example, for your ~CSS selector, this is what Nokogiri actually searches for:

 Nokogiri::CSS.xpath_for("#select_id option:starts-with('ABC')")
 #=> ["//*[@id = 'select_id']//option[starts-with(., 'ABC')]"]

The expression you wrote is not actually CSS. There is no :starts-with() pseudo-class in CSS, not even proposed in Selectors 4. What there is is the starts-with() function in XPath, and Nokogiri is (somewhat surprisingly) allowing you to mix XPath functions into your CSS and carrying them over to the XPath it uses internally.

The libxml2 library is limited to XPath 1.0, and in XPath 1.0 case-insensitive searches are done by translating all characters to lowercase. The XPath expression you'd want is thus:

//select[@id='select_id']/option[starts-with(translate(.,'ABC','abc'),'abc')]

(Assuming you only care about those characters!)

I'm not sure that you CAN write CSS+XPath in a way that Nokogiri would produce that expression. You'd need to use the xpath method and feed it that query.

Finally, you can create your own custom CSS pseudo-classes and implement them in Ruby. For example:

class MySearch
  def insensitive_starts_with(nodes, str)
     nodes.find_all{ |n| n.text =~ /^#{Regex.escape(str)}/i }
  end
end

doc.css( "select#select_id > option:insensitive_starts_with('ABC')", MySearch )

...but all this gives you is re-usability of your search code.

Upvotes: 3

Related Questions