Reputation: 7392
I have a string with the following css that I need to process with 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;
}
How can I add the prefix .page
to each rule so the css doesn't break?
I want this result
.page h1
{
color: red;
}
.page .info
{
border: 1px dotted blue;
padding: 10px;
}
...
Right now, I solve it with looking for the indentation, but the code fails on this case
h1
{
color: red;
}
Which then ends up with
.page h1
{
.page color: red;
}
I could also look for rows that only has brackets, but then this case would fail
h1 { color: red; }
I don't want to build my own css parser and the ones I found mostly handles css applied to elements in the DOM and not strings with css. Is there a good parser or can this be achieved otherwise?
Upvotes: 6
Views: 3457
Reputation: 9513
In case you don't want to use Regex, and take advantage of postcss, you can install postcss-prefix-selector and do this:
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
Output returned:
.my-prefix ul { padding: 20px 0; flex: 1; } .my-prefix li { font-family:'Lato'; color: whitesmoke; line-height: 44px; }
Upvotes: 0
Reputation: 5852
Here is a function to prefix all selectors with a class name. This function properly handle the @ rules:
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'));
If you only want to have the class name for the html
and body
selectors, add this:
rules = rules.replace(/( html| body)/g, '');
Upvotes: 10
Reputation:
You've posed as a question how to do what you think is the solution to your actual problem, which seems to be, how do I apply some CSS only to one section of a document?
Eventually, the solution will be <style scoped>
. While we're waiting for that, you can try a polyfill such as https://github.com/PM5544/scoped-polyfill, http://thomaspark.co/2015/07/polyfill-for-scoped-css/, https://github.com/thingsinjars/jQuery-Scoped-CSS-plugin, or https://github.com/thomaspark/scoper.
If you have a string of CSS (which you got from where by the way?), then to use these polyfills, you can simply insert a <style scoped>
element into your DOM under the .page
element, containing the CSS you've got in the string.
All of these polyfills have the major advantage that they are based on CSS as correctly parsed by the browser, and will not break at the first sign of trouble, as the regexp solution you inadvisably accepted, and all other regexp solutinos, inevitably will.
Upvotes: 1
Reputation: 3042
One solution, it to loop over CSS rules and update them on the fly.
/* 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>
Upvotes: 0
Reputation: 1243
Take a look at regular expressions
for example something like this:
/([^]*?)({[^]*?}|,)/g
should capture all the selectors in your css string in first group and and rules in the second group. And then you do something like this to replace the matches
var str = "your css text here";
re = /([^]*?)({[^]*?}|,)/g;
var new_str = str.replace(re, ".prefix $1 $2"); //prefixed css
console.log(new_str);
If you are not familiar with regular expressions I would recommend reading relevant chapters in Eloquent Javascript
Upvotes: -1
Reputation: 1792
If you would use sass you can simply write something like
.page {
h1 {
color: red;
}
h2 {
color: green;
}
}
This would be compiled to exactly what you want. But if this isn't an option, you have to write your own parser.
Upvotes: 0