CSS parser.
Parser limitations
At-rules (
@media,@font-face,@import, etc.) and comments are stripped.Specificity for
:not(...),:is(...), and:where(...)is approximated as a single pseudo-class. In Selectors Level 4, specificity is derived from the argument (or is0for:where).
Summary
Types
A CSS rule declaration.
A CSS pseudo-class or pseudo-element.
A CSS rule with computed specificity.
A list of selector steps ordered from right to left.
A single element step in a selector.
CSS specificity, matching the Selectors Level 4 four-tuple model.
Functions
Combines CSS rules into a final list of declarations using the CSS Cascade Level 4 algorithm.
Parses a CSS string into a list of CSS rules.
Parses a CSS declaration block string into a list of declarations.
Parses a selector string into a list of selector groups.
Splits a comma-separated selector string into individual selectors.
Converts a rule declaration or list of declarations into a string.
Types
A CSS rule declaration.
@type pseudo() :: %{ :name => String.t(), :expression => String.t() | nil, :kind => :pseudo_class | :pseudo_element, optional(:nth) => {integer(), integer()} | :invalid }
A CSS pseudo-class or pseudo-element.
Pseudo-classes in the an+b family (:nth-child, :nth-of-type,
:nth-last-child, :nth-last-of-type) include an additional :nth field
holding the parsed {a, b} coefficients, or :invalid if the expression is
missing or cannot be parsed.
@type rule() :: %{ declarations: [declaration()], selector: String.t(), specificity: specificity() }
A CSS rule with computed specificity.
@type selector_group() :: [selector_group_step()]
A list of selector steps ordered from right to left.
@type selector_group_step() :: %{ tag: String.t() | nil, id: String.t() | nil, classes: [String.t()], attrs: [String.t() | {String.t(), String.t()}], pseudos: [pseudo()], combinator: :descendant | :child | :adjacent | :sibling | :column | nil }
A single element step in a selector.
@type specificity() :: {0..1, non_neg_integer(), non_neg_integer(), non_neg_integer()}
CSS specificity, matching the Selectors Level 4 four-tuple model.
Structured as {inline?, ids, classes, elements}:
inline?-1if the style is inline.ids- number of ID selectors.classes- number of class, attribute, and pseudo-class selectors.elements- number of tag and pseudo-element selectors.
Functions
@spec cascade([rule()]) :: [declaration()]
Combines CSS rules into a final list of declarations using the CSS Cascade Level 4 algorithm.
Conflicts between declarations for the same property are resolved by
!important and then by specificity.
See https://www.w3.org/TR/css-cascade-4/#cascading for details.
Examples
iex> rules = Premailex.CSSParser.parse("p {background-color: #fff !important; color: #000;} p {background-color: #000;}")
iex> Premailex.CSSParser.cascade(rules)
[
%{property: "background-color", value: "#fff !important", important?: true},
%{property: "color", value: "#000", important?: false}
]
Parses a CSS string into a list of CSS rules.
Ignores at-rules (e.g. @media, @font-face, etc.) and comments, as they
are not relevant for inlining CSS.
Examples
iex> Premailex.CSSParser.parse("body { background-color: #fff !important; color: red; }")
[
%{
declarations: [
%{property: "background-color", value: "#fff !important", important?: true},
%{property: "color", value: "red", important?: false}
],
selector: "body",
specificity: {0, 0, 0, 1}
}
]
@spec parse_declaration_block(String.t()) :: [declaration()]
Parses a CSS declaration block string into a list of declarations.
Each declaration is a map containing the property, value, and a !important
flag.
Examples
iex> Premailex.CSSParser.parse_declaration_block("background-color: #fff; color: red;")
[
%{property: "background-color", value: "#fff", important?: false},
%{property: "color", value: "red", important?: false}
]
@spec parse_selector_groups(String.t()) :: [selector_group()]
Parses a selector string into a list of selector groups.
Each selector group is a list of steps, where each step is a map containing the tag, id, classes, attributes, pseudo-classes/elements, and combinator. Steps are ordered right to left.
Selectors that fail to parse are dropped and a debug log is emitted.
Examples
iex> Premailex.CSSParser.parse_selector_groups("div.foo")
[[%{tag: "div", id: nil, classes: ["foo"], attrs: [], pseudos: [], combinator: nil}]]
iex> Premailex.CSSParser.parse_selector_groups("body > p")
[
[
%{tag: "p", id: nil, classes: [], attrs: [], pseudos: [], combinator: nil},
%{tag: "body", id: nil, classes: [], attrs: [], pseudos: [], combinator: :child}
]
]
Splits a comma-separated selector string into individual selectors.
Examples
iex> Premailex.CSSParser.split_selector_groups("div, .foo")
["div", ".foo"]
iex> Premailex.CSSParser.split_selector_groups("div > p, a[href]")
["div > p", "a[href]"]
@spec to_string([declaration()] | declaration()) :: String.t()
Converts a rule declaration or list of declarations into a string.
Examples
iex> declarations = Premailex.CSSParser.parse_declaration_block("background-color: #fff !important; color: red;")
iex> Premailex.CSSParser.to_string(declarations)
"background-color: #fff !important; color: red;"
iex> declarations = Premailex.CSSParser.parse_declaration_block("background-color: #fff")
iex> Premailex.CSSParser.to_string(declarations)
"background-color: #fff;"