Module: Dommy::Internal::SelectorParser
- Defined in:
- lib/dommy/internal/selector_parser.rb
Overview
A CSS Selectors (Level 4) validator: parses a selector string against the grammar and raises on anything syntactically invalid, so querySelector/querySelectorAll/matches/closest throw a SyntaxError for exactly the inputs the spec requires — cases Nokogiri’s CSS parser silently accepts (‘[*=test]`, `div % p`, `..x`) or rejects with the wrong error.
This only VALIDATES; matching is still delegated to the backend. It is a hand-written tokenizer + recursive-descent parser covering the productions the Selectors spec (and the WPT corpus) exercise: selector lists, combinators, type/universal selectors with namespace prefixes, id/class, attribute selectors (with matchers and case flags), and pseudo-classes / pseudo-elements (functional and simple). Because querySelector has no namespace declarations, any named namespace prefix is “undeclared” → a SyntaxError (only ‘*|`, `|`, and the default empty prefix are allowed).
Defined Under Namespace
Classes: InvalidSelector, Parser
Constant Summary collapse
- KNOWN_PSEUDO_ELEMENTS =
Pseudo-elements (used with ‘::`, plus the four legacy `:` forms). A `::x` outside this set is an unknown pseudo-element → SyntaxError.
%w[ before after first-line first-letter selection placeholder marker backdrop slotted cue file-selector-button first-letter grammar-error spelling-error target-text highlight part view-transition view-transition-group view-transition-image-pair view-transition-old view-transition-new ].to_set.freeze
- SELECTOR_LIST_FUNCTIONS =
Functional pseudo-classes (followed by ‘(…)`). `slotted`/`cue`/`part` are functional pseudo-elements handled in the `::` path.
%w[not is where has matches].to_set.freeze
- NTH_FUNCTIONS =
%w[nth-child nth-last-child nth-of-type nth-last-of-type nth-col nth-last-col].to_set.freeze
- IDENT_FUNCTIONS =
%w[lang dir].to_set.freeze
- NESTED_SELECTOR_FUNCTIONS =
%w[host host-context current].to_set.freeze
Class Method Summary collapse
-
.matchable_selector(selector) ⇒ Object
Return ‘selector` with the comma-clauses whose subject is a pseudo-element removed (`::before`, `:first-line` — they match no element, so dropping them is what querySelector should do; the backend would otherwise error or mis-parse `::`).
- .new_parser(string) ⇒ Object
-
.valid?(selector) ⇒ Boolean
True when ‘selector` parses cleanly (no raise).
-
.validate!(selector) ⇒ Object
Validate ‘selector`; raise DOMException::SyntaxError if it is not a valid selector list, else return the original string.
Class Method Details
.matchable_selector(selector) ⇒ Object
Return ‘selector` with the comma-clauses whose subject is a pseudo-element removed (`::before`, `:first-line` — they match no element, so dropping them is what querySelector should do; the backend would otherwise error or mis-parse `::`). If EVERY clause is a pseudo-element, returns a selector that matches nothing. Assumes `selector` is already known valid. Plain selectors (no `:`) are returned untouched without re-parsing.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/dommy/internal/selector_parser.rb', line 65 def matchable_selector(selector) s = selector.to_s return s unless s.include?(":") parser = Parser.new(s) parser.parse_selector_list! clauses = parser.clauses return s unless clauses.any? { |c| c[:pseudo_subject] } kept = clauses.reject { |c| c[:pseudo_subject] } kept.empty? ? ":not(*)" : kept.map { |c| c[:text] }.join(", ") rescue InvalidSelector s end |
.new_parser(string) ⇒ Object
80 81 82 |
# File 'lib/dommy/internal/selector_parser.rb', line 80 def new_parser(string) Parser.new(string) end |
.valid?(selector) ⇒ Boolean
True when ‘selector` parses cleanly (no raise).
52 53 54 55 56 57 |
# File 'lib/dommy/internal/selector_parser.rb', line 52 def valid?(selector) validate!(selector) true rescue ::Dommy::DOMException::SyntaxError false end |
.validate!(selector) ⇒ Object
Validate ‘selector`; raise DOMException::SyntaxError if it is not a valid selector list, else return the original string.
44 45 46 47 48 49 |
# File 'lib/dommy/internal/selector_parser.rb', line 44 def validate!(selector) new_parser(selector.to_s).parse_selector_list! selector rescue InvalidSelector => e raise ::Dommy::DOMException::SyntaxError, "'#{selector}' is not a valid selector: #{e.}" end |