1

I'm trying to parse the structure of some CSS to use as an input to renderkid in Node.js, kinda (but not the same as) like Parsing CSS in JavaScript / jQuery.

For instance, I want this CSS

body { font-size: 10px; }
html { font-size: 11px; }
html, body { font-size: 12px; }
@media only screen and (max-width: 600px) {
  body {
    background-color: lightblue;
  }
}

to be parsed as this object:

{
  body: {
    "font-size": "12px"
  },
  html: {
    "font-size": "12px"
  },
  "@media only screen and (max-width: 600px)": {
    body: {
      "font-size": "12px"
    },
  }
}

To avoid reinventing the wheel by writing a CSS parser, I'm using the popular css package which returns the AST of the provided CSS which in this case looks like this:

{
    "type": "stylesheet",
    "stylesheet": {
        "rules": [{
            "type": "rule",
            "selectors": ["body"],
            "declarations": [{
                "type": "declaration",
                "property": "font-size",
                "value": "10px",
                "position": {
                    "start": {
                        "line": 1,
                        "column": 8
                    },
                    "end": {
                        "line": 1,
                        "column": 23
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 1,
                    "column": 1
                },
                "end": {
                    "line": 1,
                    "column": 26
                }
            }
        }, {
            "type": "rule",
            "selectors": ["html"],
            "declarations": [{
                "type": "declaration",
                "property": "font-size",
                "value": "11px",
                "position": {
                    "start": {
                        "line": 2,
                        "column": 8
                    },
                    "end": {
                        "line": 2,
                        "column": 23
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 2,
                    "column": 1
                },
                "end": {
                    "line": 2,
                    "column": 26
                }
            }
        }, {
            "type": "rule",
            "selectors": ["html", "body"],
            "declarations": [{
                "type": "declaration",
                "property": "font-size",
                "value": "12px",
                "position": {
                    "start": {
                        "line": 3,
                        "column": 14
                    },
                    "end": {
                        "line": 3,
                        "column": 29
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 3,
                    "column": 1
                },
                "end": {
                    "line": 3,
                    "column": 32
                }
            }
        }, {
            "type": "media",
            "media": "only screen and (max-width: 600px)",
            "rules": [{
                "type": "rule",
                "selectors": ["body"],
                "declarations": [{
                    "type": "declaration",
                    "property": "background-color",
                    "value": "lightblue",
                    "position": {
                        "start": {
                            "line": 6,
                            "column": 5
                        },
                        "end": {
                            "line": 6,
                            "column": 32
                        }
                    }
                }],
                "position": {
                    "start": {
                        "line": 5,
                        "column": 3
                    },
                    "end": {
                        "line": 7,
                        "column": 4
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 4,
                    "column": 1
                },
                "end": {
                    "line": 8,
                    "column": 2
                }
            }
        }],
        "parsingErrors": []
    }
}

At the moment, I've managed to come up with this code:

"use strict"

const {
    parse: parseCSS,
} = require("css")
const _ = require("lodash")

const pickToObject = (array, ...keys) => _.fromPairs(array.map((val) => [val[keys[0]], val[keys[1]]]))

module.exports = (css) => _.merge(...parseCSS(css).stylesheet.rules.map(({
    declarations,
    selectors,
    type,
    media,
    rules,
}) => {
    if (type === "rule") return _.fromPairs(selectors.map((selector) => [selector, pickToObject(declarations, "property", "value")]))
    if (type === "media") {
        return _.fromPairs([
            [`@media ${media}`, _.merge(...rules.map(({
                selectors,
                declarations,
            }) => _.fromPairs(selectors.map((selector) => [selector, pickToObject(declarations, "property", "value")]))))],
        ])
    }

    return undefined
}))

However, I'm not sure how to optimise it further.

Just to clarify: I need to create a canonical function that can handle any valid CSS - meaning simply grabbing values from the AST isn't an option.

Richie Bendall
  • 7,738
  • 4
  • 38
  • 58
  • 2
    The title of your question is misleading, since it isn't about parsing or CSS. What you're asking is how to extract properties from an object and rearrange them into a new object. The keys and values are completely irrelevant. There's a ton of questions already dealing with this. In case you don't know, you can get to the first rule using `astResult.stylesheet.rules[0]` –  Jan 12 '20 at 14:59
  • Here's one: https://stackoverflow.com/questions/48841569/extract-data-from-javascript-object –  Jan 12 '20 at 15:03
  • @ChrisG The premise of this problem resides around the fact that the CSS I'm showing in the question is just for example's sake. I need to be able to correctly handle the AST for when **any** valid CSS is provided which requires lots of iterating. I'll add more clarification in my question. – Richie Bendall Jan 12 '20 at 15:05
  • Do you need this? https://jsfiddle.net/khrismuc/y36zfkwg/ If that's not enough, state an example of a more complex stylesheet. –  Jan 12 '20 at 15:15
  • @ChrisG I've updated the example in my question to show the full application of the functionality I need to create. – Richie Bendall Jan 12 '20 at 15:23
  • Ok, so at this point you're essentially asking for somebody else to write the code for you. What have you tried so far? Where is your code? How did it fail? –  Jan 12 '20 at 15:30
  • @ChrisG Sure thing! – Richie Bendall Jan 12 '20 at 15:41
  • Wait... are you saying you have working code and you're just asking how to optimize it...? –  Jan 12 '20 at 16:54
  • @ChrisG I was writing the code and asked the question too early before I had finished. – Richie Bendall Jan 13 '20 at 03:40
  • In that case you might as well remove it, because you'll want to post on [codereview](https://codereview.stackexchange.com/) instead anyway. –  Jan 13 '20 at 09:48

3 Answers3

1

Using javascript it's really simple to parse your given styles object:

var styles = {
    "type": "stylesheet",
    "stylesheet": {
        "rules": [{
            "type": "rule",
            "selectors": ["body"],
            "declarations": [{
                "type": "declaration",
                "property": "background-color",
                "value": "black",
                "position": {
                    "start": {
                        "line": 1,
                        "column": 8
                    },
                    "end": {
                        "line": 1,
                        "column": 32
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 1,
                    "column": 1
                },
                "end": {
                    "line": 1,
                    "column": 33
                }
            }
        }],
        "parsingErrors": []
    }
};

var parsedStyles = {};

styles.stylesheet.rules.forEach(rule => {
  parsedStyles[rule['selectors']] = getAllStyles(rule['declarations']);
})

function getAllStyles(declarations) {
 var styles = {}
 declarations.forEach(declaration => {
    styles[declaration['property']] = declaration['value'];
  });
  
  return styles;
}

console.log(parsedStyles);
johannchopin
  • 13,720
  • 10
  • 55
  • 101
0

Solving using reduce:

let styles = {
    "type": "stylesheet",
    "stylesheet": {
        "rules": [{
            "type": "rule",
            "selectors": ["body"],
            "declarations": [{
                "type": "declaration",
                "property": "background-color",
                "value": "black",
                "position": {
                    "start": {
                        "line": 1,
                        "column": 8
                    },
                    "end": {
                        "line": 1,
                        "column": 32
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 1,
                    "column": 1
                },
                "end": {
                    "line": 1,
                    "column": 33
                }
            }
        }],
        "parsingErrors": []
    }
}

let parsedStyle = styles.stylesheet.rules.reduce((parsed, rule) => {
    parsed[rule['selectors']] = rule['declarations'].reduce((rr, r) => {
    rr[r['property']] = r['value'];return rr
    }, {})
    return parsed
}, {})

console.log(parsedStyle)
-1

You can use header only css parser https://github.com/srgank/css_parser #include "cparsecss.hpp" #include #include

int main()
{
    string css =
    " h1{ "
    " color: white; "
    " text-align: center; "
    " } "
    " "
    " p{ "
    " font-family: verdana; "
    " font-size: 20px; "
    " } "
    " "
    " b2{ "
    " font-family: verdana; "
    " font-size: 20px; "
    " } "
    ;

    CParseCSS p;  
    p.parse(css);  
    typeListData data = p.getData();  
    
    for (size_t i = 0; i < data.size(); i++) {  
        cout << data.at(i).selector << " " << data.at(i).name << " " << data.at(i).value << endl;  
    }  
}

result:

h1 color white
h1 text-align center
p font-family verdana
p font-size 20px
b2 font-family verdana
b2 font-size 20px
Mustafa Poya
  • 2,615
  • 5
  • 22
  • 36
  • 2
    I believe OP was looking for an answer applicable to nodejs, not C++. In any case, whatever code you provide is more useful if it is readable, using a Markdown code block, with correct indentation, and without obvious typos. – rici Oct 28 '22 at 16:42