cssparser
is a CSS parser (written in vanilla R) which can:
read_css()
read a cascading style sheet consisting of multiple selectors into an R listread_inline_style()
read a single inline style string into an R listcss_apply()
determine the calculated style for each element in an html documentcss_apply_inline()
add the calculated style an inline <style>
tag to every element in an HTML documentcss_merge()
, style_merge()
recursively merge multiple style sheets or stylescss_pretty_print()
, style_pretty_print()
helpers to view the R list structures in a way that may be more meaningful if you are comparing the information to its original CSS form.You can install from GitHub with:
# install.package('remotes')
remotes::install_github('coolbutuseless/cssparser')
border
is is a way of combining border-width
, border-style
and border-color
in a single property. These shorthand properties have particular inheritance rules, and currently only the font
shorthand property is the only one which is expanded and inhertied correctly.all
selector is not implemented yet.inherit
and unset
are not yet handled.@
-prefixed selectors (e.g. @media
) aren’t really handled well. They exist in the parsed data, but have not been testedwidth
of any particular element, you’ll have to do your own device-specific shenanigans.
library(cssparser)
stylesheet <- '
#main {
background-color: red !important;
color: blue;
}
.footer {
border-color: rgb(50 100 255);
font-weight: lighter;
margin-left: 1cm;
}
.footer > p {
background-color: green;
}
'
css <- cssparser::read_css(stylesheet)
css
> $`#main`
> $`#main`$`background-color`
> [1] "red"
> attr(,"important")
> [1] TRUE
>
> $`#main`$color
> [1] "blue"
>
>
> $.footer
> $.footer$`border-color`
> [1] "rgb(50 100 255)"
>
> $.footer$`font-weight`
> [1] "lighter"
>
> $.footer$`margin-left`
> [1] "1cm"
>
>
> $`.footer > p`
> $`.footer > p`$`background-color`
> [1] "green"
cssparser::css_pretty_print(css)
> #main {
> background-color: red !important;
> color: blue;
> }
>
> .footer {
> border-color: rgb(50 100 255);
> font-weight: lighter;
> margin-left: 1cm;
> }
>
> .footer > p {
> background-color: green;
> }
The object returned by read_css()
is a nested list of name/value pairs.
The top-level name is the CSS Selector (e.g. #main
). The value it points to is another named list - property/value pairs e.g. color = blue
To extract a single style from the CSS object, just subset it as you would for any R list
css[['.footer']][['font-weight']]
> [1] "lighter"
css[['#main']][['background-color']] # Note the 'important' attribute on this CSS declaration
> [1] "red"
> attr(,"important")
> [1] TRUE
Some tools are included for extracting parsing CSS values into R values e.g.
css_colour_to_hex()
will convert rgb()
and hsl()
strings to R colours. As well as CSS colour names and 3 letter hex colours (e.g. #fab
-> #ffaabb
)css_string_as_pixels()
has a (naive) go at converting length values to a number of pixels (assuming 96dpi display). This is a wrapper around two other functions:
css_string_as_css_length()
to convert a string to a numeric value, and include the unit
as an attribute.css_length_as_pixels()
to convert a value with a unit
attribute to a length in pixels
colour <- css[['.footer']][['border-color']]
colour
> [1] "rgb(50 100 255)"
cssparser::css_colour_to_hex( colour )
> [1] "#3264FFFF"
value <- css[['.footer']][['margin-left']]
value
> [1] "1cm"
cssparser::css_string_as_css_length(value)
> [1] 1
> attr(,"unit")
> [1] "cm"
cssparser::css_string_as_pixels(value)
> [1] 37.79528
Given css
and an html/xml
document, cssparser
can produce a named list containing the computed style for every element.
The names in this list are the xpaths
for each element, and the values are the named list of property/value declarations on this element.
The calculation of the style for each element takes into account:
<tag>
, #id
and .class
selectors#main > div
In the computed_styles
for the following HTML document, note the following:
<div>
(with id="main"
) has xpath = "/html/body/div[1]"
has a background-color
of red
as the !important
flag was set in the stylesheet and this overrides the inline style background-color: yellow
.footer > p
has correctly applied background-color = "green"
to only the second <p>
in the document (with xpath = "/html/body/div[2]/p"
)border
-related properties on the <div>
elements are not evident on the child <p>
elements as border properties are not inherited.
html <- xml2::read_html('
<html>
<div id="main" class="thin" style="border: 2px; background-color: yellow;">
<p>Lorem Ipsum</p>
</div>
<div class="footer">
<p>Goodbye</p>
</div>
</html>')
computed_styles <- cssparser::css_apply(html, css)
cssparser::css_pretty_print(computed_styles)
> /html/body/div[2] {
> border-color: rgb(50 100 255);
> font-weight: lighter;
> margin-left: 1cm;
> }
>
> /html/body/div[2]/p {
> font-weight: lighter;
> background-color: green;
> }
>
> /html/body/div[1] {
> background-color: red !important;
> color: blue;
> border: 2px;
> }
>
> /html {
> }
>
> /html/body {
> }
>
> /html/body/div[1]/p {
> color: blue;
> }
The following code shows a manual way of combining styles from a number of different sources using style_merge()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# StyleSheet
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
stylesheet <- '
#greg {
fill: red !important;
color: blue;
}
.thin {
border: 1px;
border-color: hsl(30 50% 40%);
font-weight: lighter;
}'
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Example HTML
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
html <- xml2::read_html('
<div id="greg" class="thin" style="border: 2px; fill: yellow;">
Lorem Ipsum
</div>')
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Convert stylesheet into rulesets (i.e. R lists indexed by selector)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
css <- read_css(stylesheet)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Manually calculate style for this element by merging the various styles
# which apply to this element based upon the CSS selectors
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
greg_final <- style_merge(
css[['div']],
css[[".thin"]],
css[['#greg']],
read_inline_style("border: 2px; fill: yellow;")
)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Note: "!important" property carried through.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cssparser::style_pretty_print(greg_final)
> border: 2px;
> border-color: hsl(30 50% 40%);
> font-weight: lighter;
> fill: red !important;
> color: blue;
cssparser::css_colour_to_hex(greg_final$`border-color`)
> [1] "#996633FF"