Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if CSS selector is valid

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.

like image 522
Vlad Shevchenko Avatar asked Jan 18 '16 07:01

Vlad Shevchenko


2 Answers

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')
like image 123
kornieff Avatar answered Oct 13 '22 19:10

kornieff


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;
}
like image 21
wOxxOm Avatar answered Oct 13 '22 18:10

wOxxOm