27

I'd like to parse a CSS file and add another CSS selector before every CSS selector.

From:

p{margin:0 0 10px;}
.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px;}

I'd like:

.mySelector p{margin:0 0 10px;}
.mySelector .lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px;}

But my CSS file is really complex (in fact it is the bootstrap CSS file), so the regex should match all CSS selectors.

For now, I have this regex:

([^\r\n,{};]+)(,|{)

And you can see the result here http://regexr.com?328ps, but as you can see, there are a lot of matches that shouldn't match.

For example:

text-shadow:0 -1px 0 rgba(0,

matches positive but it shouldn't

Does someone have a solution?

2
  • 1
    You could use SCSS to do this job. Commented Sep 25, 2012 at 3:39
  • 1
    Um, CSS selector syntax is quite complicated. You might want to simplify your problem. Commented Sep 25, 2012 at 3:40

10 Answers 10

37

There isn't one. CSS selectors are not an example of a "Regular Language" and so cannot be parsed by a Regular Expression. You will need to build your own parser based on the CSS grammar specification: http://www.w3.org/TR/css3-syntax/#detailed-grammar

CSS is described as an LL(1) grammar, so you're probably better off using a tool like Yacc to generate your parser for you.

Sign up to request clarification or add additional context in comments.

7 Comments

+1 It's very rare to see a correct answer on this kind of question.
The fact that a language isn't regular doesn't always mean you can't parse it with a regular expression. The reason is that modern regular expression implementations have more advanced features now that allow you to dive into slightly more complex language. For example, balanced matching... blogs.msdn.com/b/bclteam/archive/2005/03/15/396452.aspx
While the answer is technically correct (upvote!), the OP probably didn't necessarily want a parser that would handle every nuance of the CSS specification (which, as you say, is not a regular language). It is possible to write a regular expression (or two) to handle 99% of real-world CSS to accomplish the OP's needs (as evidenced below).
This is a great answer to different question. The OP doesn't want a CSS parser. He/she just wants to perform a find-and-replace on CSS selectors within a text (CSS) file. There's no need to over think things.
@OliverMoran While the OP just wants to prefix each selector, that's actually a really hard problem and not necessarily straightforward: CSS has quoted strings, for example (URIs, content:, etc) which could coincidentally contain a valid CSS rule. You could naively split a rule's selectors on the comma character, but a comma can appear inside an attribute selector as a quoted value. It's also easy for a naive approach to get messed-up by @media and @keyframes rules, for example which add another layer of nesting in CSS... and so on...
|
28

So I've finally found a REGEX that works for my requirements

([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)

The key point was to add a Positive lookahead to avoid bad matches

(?=[^}]*{)

You can see the result here: http://regexr.com?328s7

The only precaution that I can recommend is to remove every block comment:

2 Comments

This also has a problem of media-queries and @supports directives. I used this solution in scope-css package.
It matches @keyframes also, which is not a selector
6

So finally I've found a way better way to do it.

I use a LESS editor (eg: http://lesstester.com/)

and simply nest my whole css file: .mySelector{ your css here }

1 Comment

Though this does not answer the question but it is very helpful. Thanks. Anyone can use less, by using just two tags.
4

A simple and readable regex is below:

[\#\.\w\-\,\s\n\r\t:]+(?=\s*\{)

you can test it on css code in this tool`

Comments

3

It's true that you can not select css selectors using regex, but you can select css rules using regex, so you can make the job done in the inverse way:

  1. format css file in order to no have selectors and rules in the same line. your code should look like this:

    p{
    margin:0 0 10px;
    }
    .lead{
    margin-bottom:20px;
    font-size:21px;
    font-weight:200;
    line-height:30px;
    }

  2. select all rules (regex : (^(.)*;)

    p{
    margin:0 0 10px;
    }
    .lead{
    margin-bottom:20px;
    font-size:21px;
    font-weight:200;
    line-height:30px;

    }

  3. add some prefix to rules (example ####)

    p{
    ####margin:0 0 10px;
    }
    .lead{
    ####margin-bottom:20px;
    ####font-size:21px;
    ####font-weight:200;
    ####line-height:30px;

    }

  4. select all lines not beginning with ####,not a return to line, and not a } (regex : ^[^#### \n }])

    p{
    ####margin:0 0 10px;
    }
    .lead{
    ####margin-bottom:20px;
    ####font-size:21px;
    ####font-weight:200;
    ####line-height:30px;
    }

  5. add your css prefix

    .my_class_prefix p{
    ####margin:0 0 10px;
    }
    .my_class_prefix .lead{
    ####margin-bottom:20px;
    ####font-size:21px;
    ####font-weight:200;
    ####line-height:30px;
    }

  6. delete the added prefix ####

    .my_class_prefix p{
    margin:0 0 10px;
    }
    .my_class_prefix .lead{
    margin-bottom:20px;
    font-size:21px;
    font-weight:200;
    line-height:30px;
    }

2 Comments

Instead of "rule" I think you mean "property". The CSS spec defines a Style Rule as a combination of Selectors and Properties. But the regex you've posted is easily to break: it will fail to ignore a semicolon inside a quoted string.
@dai You are right, Given regex are just examples to explain my idea.
1

Faced a similar problem and used the following to extract only CSS selectors and directives without comments and media queries:

/^(?!.*@media)[\t ]*([a-zA-Z#.:*\[][^{\/]*\s*){[\s\S]*?}/

Example: https://regex101.com/r/JmjthP/5

I'm sure it's missing some edge-cases but seems to be working for me.

See this question: Ruby RegExp - Match all CSS selectors and directives

Comments

1

This is what I came up with:

(?<=\s)[^ ]*\s*\{[^\}]+\:[^\}]+\}

Comments

0

Here's the solution I used for a similar problem. I wanted to remove all css rules that started with @. For this I used the following regex:

@\s*.*\s*\{((?!\n\})[\s\S])+\n\}

It makes the assumption that the rule ends on a new line. That way it can sort of handle nested css rules.

Comments

0

While it isn't possible to write a single regular expression that matches any valid CSS selector, you can combine a couple regular expressions and a bit of logic to accomplish your goal.

The following uses Node's fs module to read the CSS, but you could get the raw CSS string however you want.

const fs = require('fs'),
    myClassName = 'mySelector',
    mySelector = '.' + myClassName,
    mySelectorRegex = new RegExp(`\\.${myClassName}([ .#[:(]|$)`),
    cssSelectorRegex = /[@.#a-zA-Z][^}]*?(?={)/g;

let css = fs.readFileSync('path/to/file.css').toString();

css = css.replace(cssSelectorRegex, match => {

    // Match is a string of selectors like '.foo' or '.foo, #bar'
    // We need to split it on the comma and handle each selector individually
    return match.split(',').map(selector => {

        selector = selector.trim();

        // Don't alter media queries, imports, or selectors that already contain '.mySelector'
        if (selector.startsWith('@') || selector.match(mySelectorRegex)) {

            return selector;

        }

        // Prepend '.mySelector ' to the selector
        return mySelector + ' ' + selector;

    // Combine the list of selectors back together
    }).join(',');

});

Comments

0

Interesting discussion - not pure reg exp but a really short JS function:

function cssNester(css, nestWith) {
  let kframes = [];
  css = css.replace(/@(-moz-|-webkit-|-ms-)*keyframes\s(.*?){([0-9%a-zA-Z,\s.]*{(.*?)})*[\s\n]*}/g, x => kframes.push(x) && '__keyframes__');
  css = css.replace(/([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)/g, x => x.trim()[0] === '@' ? x : x.replace(/(\s*)/, '$1' + nestWith + ' '));
  return '/*' + nestWith + '*/\n' + css.replace(/__keyframes__/g, x => kframes.shift());
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.