29

Update 2018: This question was asked long before PostCSS existed, and I would have probably used that.

I'd like to parse a blob of CSS into an AST so I can add prefixes to certain CSS directives.

Is there a CSS parser for JavaScript or Node that will do this?

I've searched NPM. The only useful result I've found is parser-lib, but it's stream-based and it looks like I'll need to write my own emitter for every CSS node.

Update: I also found JSCSSP, but it has no documentation...

0

8 Answers 8

12

Update: I previously mentioned JSCSSP, which is buggy seems to be abandoned. Obviously enough, the css module on NPM is the best:

css = require 'css'

input = '''
  body {
    font-family: sans-serif;
  }
  #thing.foo p.bar {
    font-weight: bold;
  }
'''

obj = css.parse input
sheet = obj.stylesheet

for rule in sheet.rules
  rule.selectors = ('#XXX ' + s for s in rule.selectors)

console.log css.stringify(obj)

Output:

#XXX body {
  font-family: sans-serif;
}
#XXX #thing.foo p.bar {
  font-weight: bold;
}
Sign up to request clarification or add additional context in comments.

3 Comments

BTW Just after you updated this (24-jul-2013), the JSCSSP project was updated with a load of fixes - glazman.org/JSCSSP/news.html
This is exactly what I've been looking to do - add a scope to the rules.
I highly recommend against using the 'css' module at this point. It uses regexes and can't parse a lot of CSS, and development stopped several years ago.
6

Here is our open source CSS parser css.js

Here is a simple parsing example :

<script type="text/javascript">
    var cssString = ' .someSelector { margin:40px 10px; padding:5px}';
    //initialize parser object
    var parser = new cssjs();
    //parse css string
    var parsed = parser.parseCSS(cssString);

    console.log(parsed);
</script>

To stringify parsed data structure into CSS string after editing

var newCSSString = parser.getCSSForEditor(parsed);

Main features of our CSS parser is :

  • It is lightweight.
  • It outputs easy to understand javascript object. No complex AST.
  • It is battle tested(and unit tested also) and constantly used in our products(JotForm Form Designer).
  • It supports media queries, keyframes and font-face rules.
  • It preserves comments while parsing.

2 Comments

The question was specifically about AST, so "No complex AST" does not help here
The linked library has been archived and had plenty of omissions w.r.t. the CSS spec so shouldn't be relied on for general purpose: github.com/jotform/css.js/issues
6

No need use external css parser,we can use native css parser

   
var sheetRef=document.getElementsByTagName("style")[0];

console.log("----------------list of  rules--------------");
for (var i=0; i<sheetRef.sheet.cssRules.length; i++){


var sheet = sheetRef.sheet ? sheetRef.sheet : sheetRef.styleSheet;


if (sheet.cssRules.length > 0) {
//console.log(sheet.cssRules[i]);
  console.log(sheet.cssRules[i].selectorText);
  console.log(sheet.cssRules[i].cssText);

                }}
.red{

color:red;
}

To Insert Rule

var sheetRef=document.getElementsByTagName("style")[0];
var sheet = sheetRef.sheet ? sheetRef.sheet : sheetRef.styleSheet;
sheet.insertRule('.foo{color:red;}', 0);

To Remove Rule all browsers, except IE before version 9

var sheetRef=document.getElementsByTagName("style")[0];
var sheet = sheetRef.sheet ? sheetRef.sheet : sheetRef.styleSheet;
sheet.removeRule (0);

To Delete Rule all browsers, except IE before version 9

var sheetRef=document.getElementsByTagName("style")[0];
var sheet = sheetRef.sheet ? sheetRef.sheet : sheetRef.styleSheet;
sheet.deleteRule (0);

To add Media

  function AddScreenMedia () {
            var styleTag = document.getElementsByTagName("style")[0];

                // the style sheet in the style tag
            var sheet = styleTag.sheet ? styleTag.sheet : styleTag.styleSheet;

            if (sheet.cssRules) {   // all browsers, except IE before version 9
                var rule = sheet.cssRules[0];
                var mediaList = rule.media;

                alert ("The media types before adding the screen media type: " + mediaList.mediaText);
                mediaList.appendMedium ("screen");
                alert ("The media types after adding the screen media type: " + mediaList.mediaText);
            }
            else {  // Internet Explorer before version 9
                    // note: the rules collection does not contain the at-rules
                alert ("Your browser does not support this example!");
            }
        }
  @media print {
            body {
                font-size: 13px;
                color: #FF0000;
            }
          }
some text
<button onclick="AddScreenMedia ();">Add screen media</button>

To get rules

 
var sheetRef=document.getElementsByTagName("style")[0];

console.log("----------------list of  rules--------------");
for (var i=0; i<sheetRef.sheet.cssRules.length; i++){


var sheet = sheetRef.sheet ? sheetRef.sheet : sheetRef.styleSheet;


if (sheet.cssRules.length > 0) {
//console.log(sheet.cssRules[i]);
  console.log(sheet.cssRules[i].selectorText);
  console.log(sheet.cssRules[i].cssText);
  
  console.log(sheet.cssRules[i].style.color)
  console.log(sheet.cssRules[i].style.background)
    console.log(sheet.cssRules[i].style)

                }}
.red{

color:red;
background:orange;
}
<h1>red</h1>

1 Comment

native parser is a good solution, but only in some cases. If you need only parse css without applying selectors to the document this solution isn't what you want, but there is simple resolution: you can create 'vitual' document from DOMParser. The worst thing is very poor performance when document styles are modyfing, in my browser adding <link> with big css (30MB) to existing document may take even 300ms!
5

Also worth mentioning is LESS. While it is primarily a (fantastic) extension to CSS, the LESS parser does give you access to the AST.

A pure CSS stylesheet is also a valid LESS stylesheet, so you can start with what you have now and ease in to LESS' extensions.

1 Comment

Just an update for latecomers: it looks like the LESS project won't support direct access to the parser in the future: lesscss.org/usage/index.html#programmatic-usage. Of course, you can always just pick a current/past version and use that.
4

For what it's worth, JSDOM uses CSSOM.

3 Comments

This library seems to be a third the size of JSCSSP in terms of lines of code, probably due to everything it doesn't attempt to support. For HTML5 only projects, this is the way to go.
This is awesome, I was wondering if there was a direct object model instead of these crazy AST that all the other libraries have.
One thing to note with the CSSOM is that if you parse the following shorthand: background: var(--my-color) it breaks it down into longhand properties, and throws away the variable. See bugs.chromium.org/p/chromium/issues/detail?id=1218159#c13 for browser devs pointing to omissions in the spec on this point.
2

Edit

I ended up using this library which was light enough for my implementation (provided in Kemal Dağ's answer). Other options were too heavy for the client-side implementation I was after.

https://github.com/jotform/css.js

Original Content

a paid nerd's original answer worked great until I hit media queries.

I had to add some recursion and this is what I ended up with.

Forgive me for the TypeScript.

TypeScript Implementation

private scopeCSS(css: string): CSS.Stylesheet {
  let ast: CSS.Stylesheet = CSS.parse(css);
  let stylesheet: CSS.StyleRules|undefined = ast.stylesheet;
  if (stylesheet) {
    let rules: Array<CSS.Rule|CSS.Media> = stylesheet.rules;
    let prefix = `[data-id='sticky-container-${this.parent.id}']`;

    // Append our container scope to rules
    // Recursive rule appender
    let ruleAppend = (rules: Array<CSS.Rule|CSS.Media>) => {
      rules.forEach(rule => {
        let cssRule = <CSS.Rule>rule;
        let mediaRule = <CSS.Media>rule;
        if (cssRule.selectors !== undefined) {
          cssRule.selectors = cssRule.selectors.map(selector => `${prefix} ${selector}`);
        }
        if (mediaRule.rules !== undefined) {
          ruleAppend(mediaRule.rules);
        }
      });
    };

    ruleAppend(rules);
  }
  return ast;
}

Babel'ized Vanilla JS Implementation

function scopeCSS(css, prefix) {
  var ast = CSS.parse(css);
  var stylesheet = ast.stylesheet;
  if (stylesheet) {
    var rules = stylesheet.rules;
    // Append our container scope to rules
    // Recursive rule appender
    var ruleAppend = function(rules) {
      rules.forEach(function(rule) {
        if (rule.selectors !== undefined) {
          rule.selectors = rule.selectors.map(function(selector) {
            return prefix + " " + selector;
          });
        }
        if (rule.rules !== undefined) {
          ruleAppend(rule.rules);
        }
      });
    };
    ruleAppend(rules);
  }
  return ast;
}

2 Comments

Warning: jotform's css.js can't parse all CSS (it's built with regexes). It may mangle or lose some of your input.
@erjiang to be more precise, it will destroy all rules with ';' character, since rules are simply split by line: rules = rules.split(';'); so rules like: '...url("data:image/svg+xml;charset=utf8,...' are cut and you get garbage on the output
1

http://www.glazman.org/JSCSSP/

http://jsfiddle.net/cYEgT/

sheet is sort of like an AST.

Comments

0

Very simple, one function.

function parseCSStxt(cssXTX){
    var p2=[], p1=cssXTX.split("}");
    p1.forEach(element => {
        var rtmp=element.split("{") ;
        if( rtmp.length>1 && Array.isArray(rtmp) ){
            var s=rtmp[0].split(",");
            var v=rtmp[1].split(";");
            const notNil = (i) => !(typeof i === 'undefined' || i === null || i=='');
            s = s.filter(notNil);
            v = v.filter(notNil);
            p2.push( {s,v} );
        }        
    });

    console.log(p2);
    }
    parseCSStxt( ".cls-1,.cls-5{fill:none;}.cls-1,.cls-2,.cls-5,.cls-6{stroke:#000;}.cls-1{stroke-linecap:round;stroke-linejoin:round;}.cls-1,.cls-2,.cls-6{stroke-width:4px;}.cls-2{fill:#ffad17;}.cls-2,.cls-5,.cls-6{stroke-miterlimit:10;}.cls-3{fill:#d86212;}.cls-4{fill:#87270e;}.cls-5{stroke-width:2px;}.cls-6{fill:#006d31;}" );

3 Comments

parseCSStxt("@keyframes foo {0% {color: orange} 100% {color:black}}") is valid CSS that isn't parsed meaningfully by this code.
Yep, no parser keyframes, mediaquerys,....
Also you can't just split on those characters, as CSS values can contain strings e.g. background-image: url('svg+xml;123{you get the idea}');

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.