使用JavaScript为CSS规则添加前缀

6

我有一个包含以下CSS的字符串,需要使用JavaScript进行处理

h1
{
    color: red;
}

.info
{
    border: 1px dotted blue;
    padding: 10px;
}

#rect
{
    background: pink;
}

h2,
h3,
h4
{
    font-weight: bold;
}

table td:last td
{
    background: gainsboro;
}

我该如何为每个规则添加前缀.page,以免破坏CSS?

我希望得到这样的结果:

.page h1
{
    color: red;
}

.page .info
{
    border: 1px dotted blue;
    padding: 10px;
}

...

目前,我通过查找缩进来解决这个问题,但是在这种情况下,代码会失败。

h1
{
color: red;
}

最终导致

.page h1
{
.page color: red;
}

我也可以查找只有括号的行,但那样的话这种情况就会失败。
h1 { color: red; }

我不想自己构建CSS解析器,而我找到的大多数解析器主要处理应用于DOM元素的CSS,而不是包含CSS的字符串。是否有一个好的解析器或者可以通过其他方式实现这个目标?


你需要解析它! - Salman A
这个讨论可能对这种情况有所帮助: http://stackoverflow.com/questions/8302437/how-to-parse-and-extract-css-selectors-as-strings - CmajSmith
你应该看一下rework工具套件(https://github.com/reworkcss/rework)。它可以解析和转换,也可以处理字符串。已经很明显,使用正则表达式来解析语言是一个糟糕的想法。就像你发现的那样,你会得出一些你认为可行的解决方案,然后紧接着你会发现有一种情况你的“解决方案”会失效。 - user663031
6个回答

10

这里是一个函数,可以给所有选择器添加一个类名前缀。该函数正确处理@规则:

var prefixCssSelectors = function(rules, className) {
  var classLen = className.length,
    char, nextChar, isAt, isIn;

  // makes sure the className will not concatenate the selector
  className += ' ';

  // removes comments
  rules = rules.replace( /\/\*(?:(?!\*\/)[\s\S])*\*\/|[\r\n\t]+/g, '' );

  // makes sure nextChar will not target a space
  rules = rules.replace( /}(\s*)@/g, '}@' );
  rules = rules.replace( /}(\s*)}/g, '}}' );

  for (var i = 0; i < rules.length-2; i++) {
    char = rules[i];
    nextChar = rules[i+1];

    if (char === '@' && nextChar !== 'f') isAt = true;
    if (!isAt && char === '{') isIn = true;
    if (isIn && char === '}') isIn = false;

    if (
      !isIn &&
      nextChar !== '@' &&
      nextChar !== '}' &&
      (
        char === '}' ||
        char === ',' ||
        ((char === '{' || char === ';') && isAt)
      )
    ) {
      rules = rules.slice(0, i+1) + className + rules.slice(i+1);
      i += classLen;
      isAt = false;
    }
  };

  // prefix the first select if it is not `@media` and if it is not yet prefixed
  if (rules.indexOf(className) !== 0 && rules.indexOf('@') !== 0) rules = className+rules;

  return rules;
}


// Some examples:
console.log(prefixCssSelectors('div { width: 100%; }', '.page'));

console.log(prefixCssSelectors('@charset "utf-8"; div { width: 100%; }', '.page'));

console.log(prefixCssSelectors('@media only screen { div { width: 100%; } p { size: 1.2rem; } } @media only print { p { size: 1.2rem; } } div { height: 100%; font-family: "Arial", Times; }', '.page'));

console.log(prefixCssSelectors('@font-face { font-family: "Open Sans"; src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"); } div { width: 100%; }', '.page'));

如果你只想获取 htmlbody 选择器的类名,可以添加以下内容:
rules = rules.replace(/( html| body)/g, '');

非常优雅的解决方案,在我的两个项目中都有使用,但要小心@font-face。 - Luca Trazzi
我在设置isAt = true的那一行做了一个简单的解决方法,通过检查nextChar是否不是'f'来实现,代码如下:if (char === '@' && nextChar !== 'f') isAt = true; - Luca Trazzi

1

您把如何实现您认为是解决实际问题的方法作为问题提出来了,而这个问题似乎是:如何仅将某些CSS应用于文档的某一部分?

最终的解决方案将是<style scoped>。在等待它的同时,您可以尝试使用类似https://github.com/PM5544/scoped-polyfill, http://thomaspark.co/2015/07/polyfill-for-scoped-css/, https://github.com/thingsinjars/jQuery-Scoped-CSS-plugin, 或 https://github.com/thomaspark/scoper 的polyfill。

如果你有一串CSS代码(顺便问一下,你是从哪里得到的?),那么要使用这些polyfill,你可以在.page元素下面的DOM中插入一个<style scoped>元素,其中包含你已经获取的CSS代码。所有这些polyfill都具有重大优势,即它们基于浏览器正确解析的CSS,并且不会在出现问题的第一个迹象时中断,就像你不明智地接受的regexp解决方案和所有其他regexp解决方案一样。

不幸的是,<style scoped>永远不会出现... - neo post modern

0
一种解决方案是循环遍历CSS规则并实时更新它们。

/* debug purpose */
var j = JSON.stringify;
var el = document.getElementById('el');
var log = function( val ){el.innerHTML+='<div>'+val+'</div>'}

var doIt = function(){

// you have to identify your stylesheet to get it
var ss = document.styleSheets[0];
// we get all the rules from this stylesheets
var rules = ss.cssRules;

// we loop over all rules
for( var i = 0 ; i < rules.length; i++){
  var rule = rules[i];
  
  var selector = rule.selectorText;
  var def = rule.cssText.replace(selector , '');
  
  // we update the selector
  var selector2 = selector.replace(/([^,]+,?)/g , '.page $1 ' ); 

  // we modify the rules on the fly !
  var def2 = def.replace('red' , 'green');
  def2 = def2.replace('blue' , 'red');
  def2 = def2.replace('pink' , 'lime');
  
  log(i);
  log( 'def : ' + def);
  log( 'modified : ' + def2);
  log( 'selector : ' + selector);
  log( 'updated : ' + selector2);
  log('----------');
  ss.deleteRule(i);// we remove the old 
  ss.insertRule(selector2 + def2 , i);// we add the new 
  
};

  
  // we check the stylecheet !
  log( ' checking the results');
  for( var i = 0 ; i < rules.length; i++){
  var rule = rules[i];
  
  var curRule = rule.cssText;
  log( i + ':  curRule => ' + curRule);
 
};
  
};
h1
{
    color: red;
}

.info
{
    border: 1px dotted blue;
    padding: 10px;
}

#rect
{
    background: pink;
}

h2,
h3,
h4
{
    font-weight: bold;
}

table td:last td
{
    background: gainsboro;
}
<div class='page'>
  <div id='rect'> RECT </div>
  <div class='info'> INFO </div>
<div>
<button onclick='doIt()'>update it</button>
<div id='el'><div>


0

如果您使用 sass,您可以轻松编写类似以下的内容

.page {
  h1 {
    color: red;
  }

  h2 {
    color: green;
  }
}

这将被编译为您想要的内容。但如果这不是一个选项,您必须编写自己的解析器。


我明白了,但是我不能这样做。我需要使用JavaScript处理一个CSS字符串。 - Adrian Rosca

0

如果您不想使用正则表达式,而是利用postcss的优势,您可以安装postcss-prefix-selector并执行以下操作:

import postcss from "postcss"
import prefixer from "postcss-prefix-selector"

const inputCssString = `ul { padding: 20px 0; flex: 1; } li { font-family:'Lato'; color: whitesmoke; line-height: 44px; }`
const out = postcss()
            .use(
                prefixer({
                    prefix: `.my-prefix`,
                })
            )
            .process(inputCssString).css

输出返回:

.my-prefix ul { padding: 20px 0; flex: 1; } .my-prefix li { font-family:'Lato'; color: whitesmoke; line-height: 44px; }

-1

看一下正则表达式

例如像这样的东西:

/([^]*?)({[^]*?}|,)/g

应该在第一组中捕获您CSS字符串中的所有选择器和规则,并在第二组中。然后,您可以执行以下操作以替换匹配项

var str = "your css text here";
re = /([^]*?)({[^]*?}|,)/g;
var new_str = str.replace(re, ".prefix $1 $2"); //prefixed css
console.log(new_str); 

如果您不熟悉正则表达式,我建议阅读JavaScript高级程序设计中相关的章节。


1
这段代码无法处理像 "html, body { foo: bar; content: '}'; } div { color: red }" 这样的 CSS。同样,它也无法处理 .\{boo\} { color: red; } 这样的情况。尽管这些都是人为制造的例子,但它们仍然说明了使用正则表达式无法可靠地解析 CSS 的情况。 - user663031
对于这些情况,我想你可以修改一个RegExp来考虑转义/引用的花括号。 - CrowbarKZ

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