parser_extras.rb 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. require 'thread'
  2. module Nokogiri
  3. module CSS
  4. class Parser < Racc::Parser
  5. @cache_on = true
  6. @cache = {}
  7. @mutex = Mutex.new
  8. class << self
  9. # Turn on CSS parse caching
  10. attr_accessor :cache_on
  11. alias :cache_on? :cache_on
  12. alias :set_cache :cache_on=
  13. # Get the css selector in +string+ from the cache
  14. def [] string
  15. return unless @cache_on
  16. @mutex.synchronize { @cache[string] }
  17. end
  18. # Set the css selector in +string+ in the cache to +value+
  19. def []= string, value
  20. return value unless @cache_on
  21. @mutex.synchronize { @cache[string] = value }
  22. end
  23. # Clear the cache
  24. def clear_cache
  25. @mutex.synchronize { @cache = {} }
  26. end
  27. # Execute +block+ without cache
  28. def without_cache &block
  29. tmp = @cache_on
  30. @cache_on = false
  31. block.call
  32. @cache_on = tmp
  33. end
  34. ###
  35. # Parse this CSS selector in +selector+. Returns an AST.
  36. def parse selector
  37. @warned ||= false
  38. unless @warned
  39. $stderr.puts('Nokogiri::CSS::Parser.parse is deprecated, call Nokogiri::CSS.parse(), this will be removed August 1st or version 1.4.0 (whichever is first)')
  40. @warned = true
  41. end
  42. new.parse selector
  43. end
  44. end
  45. # Create a new CSS parser with respect to +namespaces+
  46. def initialize namespaces = {}
  47. @tokenizer = Tokenizer.new
  48. @namespaces = namespaces
  49. super()
  50. end
  51. def parse string
  52. @tokenizer.scan_setup string
  53. do_parse
  54. end
  55. def next_token
  56. @tokenizer.next_token
  57. end
  58. # Get the xpath for +string+ using +options+
  59. def xpath_for string, options={}
  60. key = "#{string}#{options[:ns]}#{options[:prefix]}"
  61. v = self.class[key]
  62. return v if v
  63. args = [
  64. options[:prefix] || '//',
  65. options[:visitor] || XPathVisitor.new
  66. ]
  67. self.class[key] = parse(string).map { |ast|
  68. ast.to_xpath(*args)
  69. }
  70. end
  71. # On CSS parser error, raise an exception
  72. def on_error error_token_id, error_value, value_stack
  73. after = value_stack.compact.last
  74. raise SyntaxError.new("unexpected '#{error_value}' after '#{after}'")
  75. end
  76. end
  77. end
  78. end