19

I have the following text as a JavaScript string

.mybox {
 display: block; 
 width: 20px; 
 height: 20px;
 background-color: rgb(204, 204, 204);
 }

I want to convert to a JavaScript Object

var mybox = {
 'display': 'block',
 'width': '20px',
 'height': '20px';
 'background-color': 'rgb(204, 204, 204)';
};

Any ideas or already made scripts?

10
  • 3
    Would you mind giving us the reason you want to do that? Maybe something you want to achieve can be achieved with cleaner piece of code. Commented Jan 24, 2012 at 13:27
  • I'm using document.styleSheets[0].cssRules[x].cssText to get the CSS text of the CSS rule. I want to convert that to a JavaScript object to read it later. Commented Jan 24, 2012 at 13:29
  • how do you want to convert it? by hand, you only need notepad and you already did it. programmatically, you should specify what programming language you'd like to use and your environment. Commented Jan 24, 2012 at 13:30
  • 1
    What is your goal with this? Do you need to handle CSS declarations in an object-oriented way? How do you need to deal with functional notations, such as calc() or rgba()? How do you want to generate the variable names? What would the variable name look like for the selector div > section:first-child ~ p:hover? Commented Jan 24, 2012 at 13:31
  • 1
    i think you mean backgroundColor instead of background-color in JS-land, right? :) Commented Sep 22, 2017 at 22:23

4 Answers 4

15

Year 2017 answer

function parseCSSText(cssText) {
    var cssTxt = cssText.replace(/\/\*(.|\s)*?\*\//g, " ").replace(/\s+/g, " ");
    var style = {}, [,ruleName,rule] = cssTxt.match(/ ?(.*?) ?{([^}]*)}/)||[,,cssTxt];
    var cssToJs = s => s.replace(/\W+\w/g, match => match.slice(-1).toUpperCase());
    var properties = rule.split(";").map(o => o.split(":").map(x => x && x.trim()));
    for (var [property, value] of properties) style[cssToJs(property)] = value;
    return {cssText, ruleName, style};
} /* updated 2017-09-28 */

Description

Passing the following cssText (string) to the function:

.mybox {
     display: block; 
     width: 20px; 
     height: 20px;
     background-color: rgb(204, 204, 204);
 }

...will give the following object:

{   cssText: ... /* the original string including new lines, tabs and spaces */, 
    ruleName: ".mybox",
    style: {
        "": undefined,
        display: "block",
        width: "20px",
        height: "20px",
        backgroundColor: "rgb(204, 204, 204)"
     }
}

User can also pass a cssText such as:

display: block; width: 20px; height: 20px; background-color: rgb(204, 204, 204);

Features:

  • Works both with CSSRule.cssText and CSSStyleDeclaration.cssText.
  • Converts CSS property names (background-color) to JS property names (backgroundColor). It handles even very erratic names, such as back%gr- -ound---color: red; (converts to backGrOundColor).
  • Enables mass modification of existing CSSStyleDeclarations (such as document.body.style) using a single call Object.assign(document.body.style, parseCSSText(cssText).style).
  • Does not fail when a property name comes without a value (an entry without a colon) nor even vice versa.
  • Update 2017-09-28: Handles new lines also in rule names, collapses white spaces.
  • Update 2017-09-28: Handles comments (/*...*/).

Quirks:

  • If the last CSS declaration in the rule ends with a semicolon, returned style will include a property with an empty name "" and an undefined value reflecting the null string following the semicolon. I consider it a correct behaviour.
  • The function will return a faulty result if property value (string literal) includes colon or semicolon or CSS comments, for example div::before {content: 'test:test2;/*test3*/';}. I don’t know how to avoid this.
  • At the moment, it converts property names with prefixes such as -somebrowser-someproperty incorrectly to SomebrowserSomeproperty instead of somebrowserSomeproperty. I want a remedy that won’t ruin the brevity of code, therefore I’ll take time to find one.

Live example

function parseCSSText(cssText) {
    var cssTxt = cssText.replace(/\/\*(.|\s)*?\*\//g, " ").replace(/\s+/g, " ");
    var style = {}, [,ruleName,rule] = cssTxt.match(/ ?(.*?) ?{([^}]*)}/)||[,,cssTxt];
    var cssToJs = s => s.replace(/\W+\w/g, match => match.slice(-1).toUpperCase());
    var properties = rule.split(";").map(o => o.split(":").map(x => x && x.trim()));
    for (var [property, value] of properties) style[cssToJs(property)] = value;
    return {cssText, ruleName, style};
} /* updated 2017-09-28 */

Example:
    var sty = document.getElementById("mystyle");
    var out = document.getElementById("outcome");
    var styRule = parseCSSText(sty.innerHTML);
    var outRule = parseCSSText(out.style.cssText);
    out.innerHTML = 
        "<b>⦁ CSS in #mystyle</b>: " + JSON.stringify(styRule) + "<br>" +
        "<b>⦁ CSS of #outcome</b>: " + JSON.stringify(outRule);
    console.log(styRule, outRule); /* Inspect result in the console. */
<style id="mystyle">
.mybox1, /* a comment
    and new lines 
    to step up the game */
    .mybox 
{
    display: block; 
    width: 20px; height: 20px;
    background-color: /* a comment
        and a new line */ 
        rgb(204, 204, 204);
    -somebrowser-someproperty: somevalue;
}
</style>

<div id="outcome" style="
    display: block; padding: 0.5em;
    background-color: rgb(144, 224, 224);
">...</div>

<b style="color: red;">Also inspect the browser console.</b>

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

5 Comments

Hey! @7vujy0f0hy please how can I edit this to convert an entire css file at once for example: .taxi { background-color: #F8F8F8; color: #999; } #car { color: blue; } #user{ color: blue; height: 20px; }
@tonypro: Good question. Not foolproof but the following expression will return an array of results for each of your rules (.taxi, #car, #user): cssText.match(/[^}]*{[^}]*}/g).map(parseCSSText) (where cssText is your string). You can also append .reduce((m,r) => ({...m, [r.ruleName]: r.style}), {}) to that expression in order to convert that array to another, presumably neater data structure. Sorry about late answer.
This works well but throws a warning: Unexpected comma in middle of array no-sparse-arrays
@DanielLefebvre: Two solutions. 1. Disable no-sparse-arrays in your ESLint. 2. Or replace constructions like var [, ruleName, rule] with var [undefined, ruleName, rule] and [,, cssTxt] with [undefined, undefined, cssTxt] in my code.
A solution for the comments is to simply remove them in a preprocessing step, for example with this regex: s = s.replace(/\/\*[^*]*\*\//g, '');
6

This is the beginning of a parser that may do what you want. Of course it needs work, especially if you want to handle any generic css that may be provided. This assumes that input css is written as you provided, with the first row being the name of the property, the last row being a '}' and so on.

If you don't want to handle only basic properties, writing a complex parser is not an easy task. For example, what if you declare something like:

input[type="text"],
table > tr:nth-child(2),
#link a:hover {
    -webkit-transition: width 2s; /* Safari and Chrome */
}

This is valid css, but how would you extract a valid javascript variable name from it? How to convert -webkit-transition into a meaningful property name? The whole task smells like you're doing it all wrong. Instead of working on a parser, I'd work on a more stable solution at all.

By the way, here is the code you may start from:

    var s = '.mybox {\n';
    s += 'display: block;\n';
    s += 'width: 20px;\n';
    s += 'height: 20px;\n';
    s += 'background-color: rgb(204, 204, 204);\n';
    s += '}\n';

    // split css by line
    var css_rows = s.split('\n'); 

    // filter out empty elements and strip ';'      
    css_rows = css_rows.filter(function(x){ return x != '' }).map(function(x){ return x.trim().replace(';', '') });

    // create object
    var json_name = css_rows[0].trim().replace(/[\.\{\ \#]/g, '');
    eval('var ' + json_name + ' = {};');
    // remove first and last element
    css_rows = css_rows.splice(1, css_rows.length-2)

    for (elem in css_rows)
    {
        var elem_parts = css_rows[elem].split(':');
        var property_name = elem_parts[0].trim().replace('-', '');
        var property_value = elem_parts[1].trim();
        eval(json_name + '.' + property_name + ' = "' + property_value + '";');
    }

Comments

1

If the CSS document is included in the html document, so that the style declarations are actually loaded, you can step through all styles in Javascript like this:

// Get all style sheet documents in this html document
var allSheets = document.styleSheets;

for (var i = 0; i < allSheets.length; ++i) {
    var sheet = allSheets[i];

    // Get all CSS rules in the current style sheet document
    var rules = sheet.cssRules || sheet.rules;
    for (var j = 0; j < rules.length; ++j) {
        var rule = rules[j];

        // Get the selector definition ("div > p:first-child" for example)
        var selector = rule.selectorText;

        // Create an empty object to put the style definitions in
        var result = {};

        var style = rule.style;
        for (var key in style) {
            if (style.hasOwnProperty(key)) {
                result[key] = style.cssText;
            }
        }

        // At this point, you have the selector in the
        // selector variable (".mybox" for example)

        // You also have a javascript object in the
        // result variable, containing what you need.

        // If you need to output this as json, there
        // are several options for this.
    }
}

If this is not what you want, like if you want to parse a CSS document and create a JavaScript source file, you need to look into lexical parsers, CSS document object models, JSON serialization, and stuff like that...

Comments

0

I have the same issue working with LeafLet trying to separate CSS styles from JavaScript code... I end up with this:

var css = {};

for (var i = 0; i < document.styleSheets.length; ++i) {
    var sheet = document.styleSheets[i];
    for (var j = 0; j < sheet.cssRules.length; ++j) {
        var rule = sheet.cssRules[j];

        var cssText = rule.cssText.slice(rule.cssText.indexOf('{')+1);
        var attrs = cssText.split(';');

        var ruleSet = {};
        for (var k = 0; k < attrs.length; ++k) {
            var keyValue = attrs[k].split(':');
            if (keyValue.length == 2) {
                var key = keyValue[0].trim();
                var value = keyValue[1].trim();
                ruleSet[key] = value;
            }
        }

        for (var testRule in ruleSet) { // We are going to add the rule iff it is not an empty object
            css[rule.selectorText] = ruleSet;
            break;
        }
    }
}

console.log(css);

This will produce something like this:

css to JavaScript object

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.