Optimize CSS expressions produced by Nokogiri

Nokogiri produces inefficient XPath expressions when given CSS
expressions such as "a.gfm". Luckily these expressions can be optimized
quite easily while still achieving the same results.

In the two cases where this optimization is applied the run time has
been reduced from around 170 ms to around 15 ms.
This commit is contained in:
Yorick Peterse 2015-12-30 18:16:53 +01:00
parent d3951dfaa1
commit 054df415f9
4 changed files with 33 additions and 2 deletions

View file

@ -10,7 +10,7 @@ module Banzai
#
class RedactorFilter < HTML::Pipeline::Filter
def call
doc.css('a.gfm').each do |node|
Querying.css(doc, 'a.gfm').each do |node|
unless user_can_see_reference?(node)
# The reference should be replaced by the original text,
# which is not always the same as the rendered text.

View file

@ -16,7 +16,7 @@ module Banzai
end
def call
doc.css('a.gfm').each do |node|
Querying.css(doc, 'a.gfm').each do |node|
gather_references(node)
end

18
lib/banzai/querying.rb Normal file
View file

@ -0,0 +1,18 @@
module Banzai
module Querying
# Searches a Nokogiri document using a CSS query, optionally optimizing it
# whenever possible.
#
# document - A document/element to search.
# query - The CSS query to use.
#
# Returns a Nokogiri::XML::NodeSet.
def self.css(document, query)
# When using "a.foo" Nokogiri compiles this to "//a[...]" but
# "descendant::a[...]" is quite a bit faster and achieves the same result.
xpath = Nokogiri::CSS.xpath_for(query)[0].gsub(%r{^//}, 'descendant::')
document.xpath(xpath)
end
end
end

View file

@ -0,0 +1,13 @@
require 'spec_helper'
describe Banzai::Querying do
describe '.css' do
it 'optimizes queries for elements with classes' do
document = double(:document)
expect(document).to receive(:xpath).with(/^descendant::a/)
described_class.css(document, 'a.gfm')
end
end
end