JavaScript的CSS解析器?

27

2018年更新:此问题是在PostCSS存在之前提出的,我可能会使用它。

我想将一块CSS解析成AST,以便在某些CSS指令中添加前缀。

是否有适用于JavaScript或Node的CSS解析器可以做到这一点?

我已经在NPM上搜索了。我找到的唯一有用的结果是parser-lib,但它是基于流的,并且看起来我需要为每个CSS节点编写自己的发射器。

更新:我还发现了JSCSSP,但它没有文档......

8个回答

12

更新: 我之前提到了JSCSSP,但它有缺陷并且似乎已经被放弃了。显然,NPM上的css模块是最好的选择:

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)

输出:

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

3
顺便提一下,就在你更新这个(2013年7月24日)之后,JSCSSP项目更新了许多修复 - http://www.glazman.org/JSCSSP/news.html - PandaWood
1
这正是我一直想要做的——为规则添加一个范围。 - CTS_AE
2
我强烈建议不要在此时使用“css”模块。它使用正则表达式,无法解析大量的CSS,并且开发已经停止了几年。 - erjiang

6

这是我们的开源CSS解析器css.js

以下是一个简单的解析示例:

<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>

将编辑后的解析数据结构转换为CSS字符串
var newCSSString = parser.getCSSForEditor(parsed);

我们的CSS解析器的主要特点如下:

  • 它非常轻巧。
  • 它输出易于理解的JavaScript对象,没有复杂的AST。
  • 它经过了实战测试(也进行了单元测试),并且在我们的产品中(JotForm表单设计师)得到了不断的使用。
  • 它支持媒体查询、关键帧和字体规则。
  • 它在解析时保留注释。

这个问题特别涉及到AST,所以“没有复杂的AST”在这里并没有帮助。 - kca
这个链接的库已经被归档,并且在CSS规范方面存在很多遗漏,因此不能依赖它用于一般目的: https://github.com/jotform/css.js/issues - EoghanM

5

值得一提的是LESS。虽然它主要是CSS的一个扩展(非常好用),但LESS解析器确实让你可以访问AST。

纯CSS样式表也是合法的LESS样式表,因此您可以从现有的CSS样式表开始,逐渐过渡到LESS的扩展。


1
只是为了那些来得比较晚的人提供一下更新:看起来 LESS 项目将不再支持直接访问解析器:http://lesscss.org/usage/index.html#programmatic-usage。当然,您始终可以选择当前或以前的版本并使用它。 - A. L. Flanagan

5

不需要使用外部CSS解析器,我们可以使用本地CSS解析器

   
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;
}

插入规则

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

移除规则 除了IE 9之前的版本外,适用于所有浏览器。

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

删除规则 所有浏览器,除了IE 9及之前版本

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

添加媒体

  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>

获取规则

 
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>


4

这个库在代码行数方面似乎只有 JSCSSP 的三分之一大小,可能是因为它不尝试支持所有功能。对于仅限于 HTML5 的项目来说,这是一个不错的选择。 - conartist6
这太棒了,我在想是否有一个直接对象模型,而不是其他库中都有的疯狂AST。 - CTS_AE
在处理CSSOM时需要注意的一点是,如果解析以下简写形式:background: var(--my-color),它会将其拆分为长手属性,并丢弃变量。请参考https://bugs.chromium.org/p/chromium/issues/detail?id=1218159#c13,浏览器开发人员指出了规范中的遗漏问题。 - EoghanM

2

编辑

最终我使用了这个库,它非常轻量级适合我的实现(在 Kemal Dağ 的回答中提供)。其他选项对于我追求的客户端实现而言过于沉重。

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

原始内容

a paid nerd 的原始答案一直很好,直到我遇到媒体查询为止。

我必须添加一些递归,这就是最终结果。

对于 TypeScript,请谅解。

TypeScript 实现

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化的Vanilla JS实现

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;
}

警告:jotform的css.js无法解析所有CSS(它是使用正则表达式构建的)。它可能会损坏或丢失您的某些输入。 - erjiang


0

非常简单,只有一个函数。

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;}" );


parseCSStxt("@keyframes foo {0% {color: orange} 100% {color:black}}") 是有效的 CSS,但这段代码无法对其进行有意义的解析。 - AKX
是的,没有解析器关键帧,媒体查询等。 - Muisca
另外,你不能只是根据这些字符进行分割,因为CSS的值可以包含字符串,例如background-image: url('svg+xml;123{你明白我的意思}'); - EoghanM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接