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?

Chuck Norris
  • 15,207
  • 15
  • 92
  • 123
Omar Abid
  • 15,753
  • 28
  • 77
  • 108
  • 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. – Tadeck Jan 24 '12 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. – Omar Abid Jan 24 '12 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. – pistacchio Jan 24 '12 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`? – Anders Marzi Tornblad Jan 24 '12 at 13:31
  • specifying your aim here is important. if you just want to retrieve it a string, just store it. if you want to modify some parameter, you can do that with javascript itself (getElementById('elem').style.display = 'none'); – pistacchio Jan 24 '12 at 13:35
  • @pistacchio It's clear that I'm dealing with a CSS object and not an element. – Omar Abid Jan 24 '12 at 13:37
  • @atornblad To get the CSS properties in an object. Simply. The property will be a string whatever it is. – Omar Abid Jan 24 '12 at 13:38
  • You're simply going to need a CSS parser written in JavaScript. What you want to do is precisely the thing that a parser does, in other words. For example, [this one](http://glazman.org/JSCSSP/). – Pointy Jan 24 '12 at 13:47
  • Thank you both, I'm working on the code and will keep you updated if you want. Thank you. – Omar Abid Jan 24 '12 at 14:28
  • 1
    i think you mean `backgroundColor` instead of `background-color` in JS-land, right? :) – Alan H. Sep 22 '17 at 22:23

4 Answers4

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>
7vujy0f0hy
  • 8,741
  • 1
  • 28
  • 33
  • 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; }` – tony pro Mar 13 '18 at 17:51
  • @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. – 7vujy0f0hy Mar 26 '18 at 01:32
  • This works well but throws a warning: Unexpected comma in middle of array no-sparse-arrays – Daniel Lefebvre Mar 20 '19 at 14:48
  • @DanielLefebvre: Two solutions. **1.** Disable [`no-sparse-arrays`](https://eslint.org/docs/rules/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. – 7vujy0f0hy Mar 21 '19 at 01:05
  • A solution for the comments is to simply remove them in a preprocessing step, for example with this regex: `s = s.replace(/\/\*[^*]*\*\//g, '');` – Waruyama May 05 '21 at 21:26
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 + '";');
    }
pistacchio
  • 56,889
  • 107
  • 278
  • 420
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...

Anders Marzi Tornblad
  • 18,896
  • 9
  • 51
  • 66
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

vargax
  • 59
  • 1
  • 2