Reputation: 768
I have a field for users to input a CSS selector and I want to check if it's valid (according to css3 specification). I tried to use expressions from css3 specification as suggested in another stackoverflow topic, but it didn't work - the regexp I built just didn't match valid selectors. What I have for now is simply:
try {
document.querySelector(selector);
} catch (e) {
// handle bad input
}
But it doesn't seem like a good solution - querySelector function is designed for getting elements, and checking of selector is just a side effect. Furthermore it doesn't provide any information about what is wrong with the selector.
What I'm looking for is something like document.validateSelector
or library for parsing CSS selectors.
Upvotes: 16
Views: 13121
Reputation: 871
Not sure it would cover all edge cases but using CSS.escape
on an invalid selector returns an empty NodeList
instead of throwing a syntax error.
document.querySelectorAll(CSS.escape('p > > > a')) => NodeList []
document.querySelectorAll(CSS.escape('a?+')) => NodeList []
Upvotes: 0
Reputation: 73526
Since querySelector and CSS.supports('selector('+s+')') accept unclosed input like a[href="foo
,
let's use the built-in CSSStyleSheet API to check the selector can be used in a CSS rule:
let isv;
function isSelectorValid(sel) {
if (!isv) {
try {
// Chrome 73 and newer
isv = new CSSStyleSheet();
} catch (e) {
// This will fail on sites with an unusually strict CSP that forbids inline styles,
// so you'll need to set `nonce` or reuse an existing `link` element.
isv = document.head.appendChild(document.createElement('style')).sheet;
isv.disabled = true;
}
}
let res = false;
try {
// the leading space skips selector's trailing escape char
const body = ` { --foo: "${Math.random()}"; }`;
isv.insertRule(sel + body);
res = isv.cssRules[0].cssText.endsWith(body);
isv.deleteRule(0);
} catch (e) {}
return res;
}
Upvotes: 1
Reputation: 2557
The problem with original idea is that it will search the entire document. Slow 🤨 !
However, searching an empty light-weight element that is not even attached to the DOM is fast ✌️!
const queryCheck = (s) => document.createDocumentFragment().querySelector(s)
const isSelectorValid = (selector) => {
try { queryCheck(selector) } catch { return false }
return true
}
console.assert(isSelectorValid('p > > > a') === false)
console.assert(isSelectorValid('p > a') === true)
console.log('Test passed')
The following version is a bit more advanced with dummy fragment enclosure:
const isSelectorValid = ((dummyElement) =>
(selector) => {
try { dummyElement.querySelector(selector) } catch { return false }
return true
})(document.createDocumentFragment())
console.assert(isSelectorValid('p > > > a') === false)
console.assert(isSelectorValid('p > a') === true)
console.log('Test passed')
Upvotes: 37
Reputation: 154
Thanks to @kornieff hint, I've reached out to an answer for nodejs using jsdom, if it can help anyone :
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const { document } = (new JSDOM('')).window;
const queryCheck = s => document.createDocumentFragment().querySelector(s)
const isSelectorValid = selector => {
try { queryCheck(selector) } catch { return false }
return true
}
console.log(isSelectorValid("a#x#y"), isSelectorValid("a?+"));
Upvotes: 1
Reputation: 19080
You can use a library to verify if the selector is valid, and probably get more details from parsing. Check the css selector parser.
Upvotes: 1