Regex to select CSS declaration blocks that don't have certain color values

Viewed 55

Let's say I have the following CSS. I need a regular expression that grabs entire declaration blocks (selector, opening bracket, contents, closing bracket) that do not have the folowing colors in them: #f518e2 and #22f1e6

.foo{
  color: #f518e2;
  box-spacing: border-box;
}

#bar{
  color: #e4e4e4;
  box-spacing: border-box;
}

biz.baz{
  background: #22f1e6;
  font-size: 30px;
}

So in this case, I would want to grab:

#bar{
  color: #e4e4e4;
  box-spacing: border-box;
}

Assumptions:

  • There are hundreds of selectors in the file
  • The syntax is always valid CSS, but spacing, new lines, and use of semi-colons is inconsistent. For example:
#bar{   color: #e4e4e4;   
box-spacing: border-box; }biz.baz
{   
  background: #22f1e6;   
font-size: 30px; }

I've tried something like this: /\w*[#.]?\w+\s*{[^\{\}]+}/g which selects entire declaration blocks, but when it comes to weeding out the ones with #f518e2 and #22f1e6 it's a little beyond what I know about regex.

2 Answers

You probably want to use JavaScript to do such a task. I've including an example for your studies:

//<[CDATA[
/* js/external.js */
function hexNum(x){
  switch(x.toUpperCase()){
    case 'A':
      return 10;
    case 'B':
      return 11;
    case 'C':
      return 12;
    case 'D':
      return 13;
    case 'E':
      return 14;
    case 'F':
      return 15;
    default:
      return +x;
  }
}
function hexToRGBArray(hex){
  let s, r = [];
  if(hex.match(/^#?[\dA-F]{6}$/i)){
    s = hex.replace(/^#/, '').split('');
    for(let i=0,q=1; i<6; i+=2,q+=2){
      r.push(hexNum(s[i])*16+hexNum(s[q]));
    }
  }
  else if(hex.match(/^#?[\dA-F]{3}/i)){
    s = hex.replace(/^#/, '').split('');
    for(let i=0,h; i<3; i++){
      h = hexNum(s[i]); r.push(h*16+h);
    }
  }
  else{
    throw new Error('invalid hexadecimal');
  }
  return r;
}
addEventListener('load', ()=>{
// stack overflow is including nodes you won't see in real world
const all = document.body.querySelectorAll('*');
const avoid1 = hexToRGBArray('#f518e2').join(), avoid2 = hexToRGBArray('#22f1e6').join();
let s;
for(let n of all){ // n being node
  s = getComputedStyle(n).color.replace(/^.+\(|\).*$/g, '').split(/\s*,\s*/).join();
  if(s !== avoid1 && s !== avoid2){
    console.log(n.textContent);
    // n is node
  }
}
}); // end load
//]]>
/* css/external.css */
div{
  color:#ab7722;
}
#test1{
  color:#f518e2;
}
#test4{
   color:#22f1e6;
}
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>
  <head>
    <meta charset='UTF-8' /><meta name='viewport' content='width=device-width, height=device-height, initial-scale:1, user-scalable=no' />
    <title>Title Here</title>
    <link type='text/css' rel='stylesheet' href='css/external.css' />
    <script src='js/external.js'></script>
  </head>
<body>
  <div id='test1'>nope</div>
  <div id='test2'>got this one</div>
  <div id='test3'>and this one</div>
  <div id='test4'>not this one</div>
</body>
</html>

Note that in the real world document.body would only access that which is in the body. Stack Overflow must have something else in mind. They should fix it.

One option could be to match all lines where { is at the start of the line, and } at the end of the line and match all lines in between that do not have either #f518e2 or #22f1e6 using a negative lookahead.

Note that this pattern works for the given examples, but does not take any nesting or data structure into account.

^.*{(?:\r?\n(?!.*#(?:f518e2|22f1e6)\b).*)*\r?\n}

The pattern matches:

  • ^ Start of string
  • .*{ Match the whole line and { at the end
  • (?: Non capture group
    • \r?\n Match a newline
    • (?!.*#(?:f518e2|22f1e6)\b).* Assert that the line does not contain the colors and match the whole line
  • )* Close the non capture group and repeat 0+ times
  • \r?\n Match a newline
  • } Match {

Regex demo

Related