lib/nokogiri/css/parser_extras.rb
# frozen_string_literal: true
require "thread"
module Nokogiri
module CSS
class Parser < Racc::Parser # :nodoc:
CACHE_SWITCH_NAME = :nokogiri_css_parser_cache_is_off
@cache = {}
@mutex = Mutex.new
class << self
# Return a thread-local boolean indicating whether the CSS-to-XPath cache is active. (Default is `true`.)
def cache_on?
!Thread.current[CACHE_SWITCH_NAME]
end
# Set a thread-local boolean to turn caching on and off. Truthy values turn the cache on, falsey values turn the cache off.
def set_cache(value) # rubocop:disable Naming/AccessorMethodName
Thread.current[CACHE_SWITCH_NAME] = !value
end
# Get the css selector in +string+ from the cache
def [](string)
return unless cache_on?
@mutex.synchronize { @cache[string] }
end
# Set the css selector in +string+ in the cache to +value+
def []=(string, value)
return value unless cache_on?
@mutex.synchronize { @cache[string] = value }
end
# Clear the cache
def clear_cache(create_new_object = false)
@mutex.synchronize do
if create_new_object
@cache = {}
else
@cache.clear
end
end
end
# Execute +block+ without cache
def without_cache(&block)
original_cache_setting = cache_on?
set_cache(false)
yield
ensure
set_cache(original_cache_setting)
end
end
# Create a new CSS parser with respect to +namespaces+
def initialize(namespaces = {})
@tokenizer = Tokenizer.new
@namespaces = namespaces
super()
end
def parse(string)
@tokenizer.scan_setup(string)
do_parse
end
def next_token
@tokenizer.next_token
end
# Get the xpath for +string+ using +options+
def xpath_for(string, prefix, visitor)
key = cache_key(string, prefix, visitor)
self.class[key] ||= parse(string).map do |ast|
ast.to_xpath(prefix, visitor)
end
end
# On CSS parser error, raise an exception
def on_error(error_token_id, error_value, value_stack)
after = value_stack.compact.last
raise SyntaxError, "unexpected '#{error_value}' after '#{after}'"
end
def cache_key(query, prefix, visitor)
if self.class.cache_on?
[query, prefix, @namespaces, visitor.config]
end
end
end
end
end