Documentation
¶
Overview ¶
Package css reads CSS. It implements the tokenizer and parser of CSS Syntax Level 3, which is the layer every other CSS specification is written on top of: selectors, properties, media queries and the rest all describe what a valid sequence of these tokens means.
Why a tokenizer rather than a regular expression ¶
CSS looks like it could be read by matching patterns, and it cannot. A string may contain a brace; a comment may contain a quote; a url() may contain unquoted parentheses; an identifier may contain an escape that encodes any code point at all, including a brace. Every one of those is a place where a pattern-matching reader silently disagrees with a browser about where a rule ends — and a stylesheet that a browser reads one way and this reads another is a rendering bug that no amount of testing the *output* will localise.
So the tokenizer is the specification's algorithm, followed step for step.
Recovery ¶
Tokenization never fails. Every malformed construct has a defined recovery — an unterminated string becomes a bad-string-token, a url() with a stray quote becomes a bad-url-token — and the token stream continues. This is not leniency: it is what makes a stylesheet with one broken rule render the other rules, which is what the specification requires and what an author expects.
The places where recovery happened are reported separately, as Errors, so that a caller can tell an author what was wrong without the reading of the document depending on it.
Index ¶
- func ParseComponentValues(input string) ([]ComponentValue, []Error)
- func ParseDeclarationValues(block []ComponentValue) ([]Declaration, []Rule, []Error)
- func ParseDeclarations(input string) ([]Declaration, []Rule, []Error)
- func ParseRules(input string) ([]Rule, []Error)
- func ParseRulesFromValues(block []ComponentValue) ([]Rule, []Error)
- func ParseSelectorList(vals []ComponentValue) (sels []Selector, errs []Error, ok bool)
- func ParseStylesheet(input string) ([]Rule, []Error)
- func Position(input string, offset int) (line, col int)
- func Tokenize(input string) ([]Token, []Error)
- type AnB
- type Attr
- type AttrOp
- type Combinator
- type ComponentValue
- type Compound
- type Declaration
- type Error
- type Kind
- type Pseudo
- type PseudoKind
- type Rule
- type Selector
- type Specificity
- type Token
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ParseComponentValues ¶
func ParseComponentValues(input string) ([]ComponentValue, []Error)
ParseComponentValues parses a list of component values (§5.3.10).
This is the entry point for anything that is a *value* rather than a stylesheet: the prelude of a rule, the value of a declaration, the arguments of a media query.
func ParseDeclarationValues ¶
func ParseDeclarationValues(block []ComponentValue) ([]Declaration, []Rule, []Error)
ParseDeclarationValues is ParseDeclarations over already-parsed input, which is what reading the block of a rule that has already been parsed needs.
Re-tokenizing the block's source text instead would be wrong as well as wasteful: the nesting was worked out once already, and a second pass over text that was recovered from — an unclosed function, say — need not reach the same answer.
func ParseDeclarations ¶
func ParseDeclarations(input string) ([]Declaration, []Rule, []Error)
ParseDeclarations parses a list of declarations (§5.3.6): the contents of a style rule's block.
Declarations and at-rules are returned separately rather than interleaved, because an at-rule inside a declaration block is CSS Nesting, which this engine does not implement — the layer above reports each one as unsupported rather than acting on it. Both carry an Offset, so a caller that does need source order can recover it without this returning a sum type that every caller would then have to switch on.
func ParseRules ¶
ParseRules parses a list of rules that is not a whole stylesheet (§5.3.4) — the contents of an @media block, say. The difference from ParseStylesheet is only the handling of "<!--" and "-->", which are historical and which a nested context has no reason to ignore.
func ParseRulesFromValues ¶
func ParseRulesFromValues(block []ComponentValue) ([]Rule, []Error)
ParseRulesFromValues is ParseRules over already-parsed component values, which is what reading the body of an @media block needs.
func ParseSelectorList ¶
func ParseSelectorList(vals []ComponentValue) (sels []Selector, errs []Error, ok bool)
ParseSelectorList parses the prelude of a style rule into selectors.
A selector that cannot be parsed, or that falls outside the subset, is dropped and reported. If *any* of them is dropped the whole list is invalid — that is what the specification requires, and it is the safe direction: a rule whose selector list was silently narrowed applies to fewer elements than its author asked for, and nothing about the resulting page says so. ok reports whether the list survived intact.
func ParseStylesheet ¶
ParseStylesheet parses a stylesheet (§5.3.3): a list of rules, at the top level, where "<!--" and "-->" are ignored rather than being read as the start of a qualified rule.
func Position ¶
Position converts a byte offset into a line and column, both counted from 1, with the column counted in code points rather than bytes — which is what an editor shows and therefore what an author can act on.
An offset that falls inside a multi-byte character names that character rather than the one after it. Token offsets are always at character boundaries, so this only arises for an offset computed some other way, and pointing one character past the problem is the wrong direction to be wrong in.
An offset past the end of the input gives the position just after the last character, which is where an "unexpected end of input" belongs.
func Tokenize ¶
Tokenize turns CSS source into tokens.
The returned slice always ends with an EOF token, so a parser reading ahead never has to bounds-check: past the end of the input there is always exactly one more token, and it says the input has ended.
Errors are the places where the input was malformed and the specification's recovery was applied. Tokenizing never fails, so they are advisory: a caller that ignores them gets the same tokens a browser would produce.
Types ¶
type AnB ¶
type AnB struct{ A, B int }
AnB is a selection of the indices A×n + B, for every integer n ≥ 0 that makes the result positive. It is what :nth-child(2n+1) means.
func ParseAnB ¶
func ParseAnB(vals []ComponentValue) (AnB, bool)
ParseAnB reads an An+B value from component values — the arguments of an :nth-child() and friends. It reports false for anything that is not one, which makes the whole selector invalid rather than matching nothing.
func (AnB) Matches ¶
Matches reports whether a one-based index is selected.
The definition is "there is a non-negative integer n such that index = A×n+B", which is a divisibility test rather than a loop — an important difference when A is large and the caller is a layout engine walking a hostile document.
type Attr ¶
type Attr struct {
// Name is the attribute name as written. HTML lowercases attribute names,
// so the layer that matches folds it; keeping it as written lets a
// diagnostic quote the author.
Name string
Op AttrOp
// Value is empty when Op is AttrExists.
Value string
// Insensitive is the "i" flag of "[a=v i]", which asks for an
// ASCII case-insensitive comparison of the *value*. The "s" flag asks for a
// sensitive one, which is the default, so it is recorded as false rather
// than as a third state.
Insensitive bool
}
Attr is one attribute selector.
type AttrOp ¶
type AttrOp uint8
AttrOp is how an attribute selector compares.
const ( // AttrExists is "[a]" — the attribute is present, whatever its value. AttrExists AttrOp = iota // AttrEquals is "[a=v]". AttrEquals // AttrIncludes is "[a~=v]" — v is one of a whitespace-separated list. AttrIncludes // AttrDashMatch is "[a|=v]" — v, or v followed by "-". It exists for // language subtags, where "en" should match "en-GB". AttrDashMatch // AttrPrefix is "[a^=v]". AttrPrefix // AttrSuffix is "[a$=v]". AttrSuffix // AttrSubstring is "[a*=v]". AttrSubstring )
type Combinator ¶
type Combinator uint8
Combinator joins two compound selectors.
const ( // Descendant is the space in "a b": b anywhere inside a. Descendant Combinator = iota // Child is ">": b directly inside a. Child // NextSibling is "+": b immediately after a. NextSibling // SubsequentSibling is "~": b anywhere after a, under the same parent. SubsequentSibling )
func (Combinator) String ¶
func (c Combinator) String() string
type ComponentValue ¶
type ComponentValue struct {
// Token is the preserved token, the function token, or the block's opening
// delimiter, depending on which of the three this node is.
Token Token
// Values is the contents of a block or the arguments of a function, and nil
// for a preserved token.
//
// A block's delimiters are not in it: they are Token and its mirror, and
// the closing one may not have been present at all if the input ended
// early. This is why the closing delimiter is not kept — after recovery
// there may not be one to keep.
Values []ComponentValue
}
A ComponentValue is one node of a parsed stylesheet: a preserved token, a function call, or a block delimited by (), [] or {}.
Which of the three it is, is read off Token.Kind, and no separate tag is needed because the parser never leaves the ambiguous kinds preserved. A Function token always became a function, and an opening delimiter always became a block, so:
- Token.Kind == Function — a function. Token.Value is its name, Values its arguments.
- Token.Kind == LeftParen, LeftSquare or LeftBrace — a block. Values is its contents.
- anything else — a preserved token, and Values is nil.
The methods below say the same thing without the caller having to remember it.
func (ComponentValue) IsBlock ¶
func (c ComponentValue) IsBlock() bool
IsBlock reports whether this node is a (), [] or {} block.
func (ComponentValue) IsFunction ¶
func (c ComponentValue) IsFunction() bool
IsFunction reports whether this node is a function call.
func (ComponentValue) IsToken ¶
func (c ComponentValue) IsToken() bool
IsToken reports whether this node is a preserved token — neither a function nor a block.
type Compound ¶
type Compound struct {
// Combinator joins this compound to the one before it. It is meaningless on
// the first compound of a selector, where it is Descendant.
Combinator Combinator
// Type is the element name, empty if none was written. Universal is "*".
// Both may be absent, which is what ".c" is.
Type string
Universal bool
// IDs is every "#name" in the compound. More than one is legal and is not a
// mistake to be corrected here: "#a#b" matches nothing, and "#a#a" matches
// what "#a" matches while counting twice towards specificity, which is a
// long-standing way to raise a rule's weight without touching the document.
// Refusing either would reject stylesheets that browsers accept.
IDs []string
Classes []string
Attrs []Attr
Pseudos []Pseudo
}
Compound is a run of simple selectors that all constrain the same element, together with the combinator joining it to the compound before it.
type Declaration ¶
type Declaration struct {
// Name is the property name as written, with escapes resolved and the case
// as the author typed it. Property names are matched case-insensitively, so
// a caller comparing this must fold case rather than compare directly.
Name string
// Value is the component values between the colon and the end of the
// declaration, with the "!important" removed if it was there and with
// leading and trailing whitespace stripped. Whitespace *within* the value
// is kept, because it separates the parts of a shorthand.
Value []ComponentValue
// Important reports that the declaration ended with "!important", which
// changes where it sorts in the cascade.
Important bool
// Offset is the byte offset in the source at which the property name
// begins.
Offset int
}
A Declaration is a property name and the value assigned to it.
type Error ¶
type Error struct {
// Offset is the byte offset in the input where the problem was noticed.
Offset int
// Message says what was wrong, in terms of the source rather than of the
// algorithm: "unterminated string", not "unexpected EOF in state 7".
Message string
// Unsupported marks correct CSS that this engine does not implement, as
// against input that is malformed. The two need telling apart because they
// mean opposite things to an author: a malformed rule is theirs to fix,
// while an unsupported one is a limit of the renderer, and a page that came
// out wrong because of one is not diagnosed by looking at the other.
//
// It is here from the first parser rather than added later, because the
// rendering proposal's §6.3 argues — and this is the cheapest guardrail it
// names — that an engine implementing a subset *will* silently ignore
// things, and that a page where a declaration was dropped is plausible and
// wrong, which is worse than one that is obviously broken.
Unsupported bool
}
Error is a place where the input was not well formed and the specification's recovery was applied.
It never stops the reading of a stylesheet. It exists so that a caller can tell an author what was wrong, which is the difference between a tool that renders a broken document and one that says why it looks wrong.
type Kind ¶
type Kind uint8
Kind is what a token is.
const ( EOF Kind = iota // Ident is a bare name: a property name, a keyword, an element name. Ident // Function is a name immediately followed by "(" — the "(" is part of the // token, which is what distinguishes rgb( from the ident rgb. Function // AtKeyword is "@" followed by a name: @media, @page, @font-face. AtKeyword // Hash is "#" followed by a name or escape. IsID says whether the name is // also a valid identifier, which is what separates the selector #main from // the colour #123. Hash // String is a quoted string, with the quotes removed and escapes resolved. String // BadString is an unterminated string: recovery for a quote left open at // the end of a line. BadString // URL is the unquoted form, url(foo.png). The quoted form is a Function // followed by a String, because that needs no special tokenization. URL // BadURL is an unquoted url() containing something that cannot appear in // one. BadURL // Delim is a single code point with no other meaning: an operator such as // "*", "+" or "/", or a stray character. Delim // Number is a numeric value. IsInteger says whether it was written without // a fractional part or exponent, which some properties care about. Number // Percentage is a number followed by "%". Percentage // Dimension is a number followed by a unit: 12px, 1.5em, 90deg. Dimension // Whitespace is a run of spaces, tabs and newlines collapsed into one // token. It is significant in CSS — it is the descendant combinator — so it // is a token rather than something skipped. Whitespace // CDO and CDC are "<!--" and "-->", which exist so that a stylesheet could // be embedded in an HTML comment for the benefit of browsers that predate // the style element. They are tokens because the specification says so; a // stylesheet that uses them meaningfully has not been written since 1998. CDO CDC Colon Semicolon Comma LeftSquare RightSquare LeftParen RightParen LeftBrace RightBrace )
The token types of CSS Syntax Level 3 §4. EOF is the zero value so that reading past the end of a token slice yields end-of-file rather than an identifier.
type Pseudo ¶
type Pseudo struct {
Kind PseudoKind
// Name is the pseudo-class as the author wrote it, for diagnostics.
Name string
// AnB is set for the four :nth-* kinds.
AnB AnB
// Of is the "of S" of ":nth-child(An+B of S)", empty when absent.
Of []Selector
// Args is set for :not(), :is() and :where().
Args []Selector
// Langs is set for :lang().
Langs []string
}
Pseudo is one pseudo-class in a compound selector.
type PseudoKind ¶
type PseudoKind uint8
PseudoKind names a pseudo-class this engine implements. Anything not here is refused at parse time, so there is no kind for "unknown".
const ( // Structural pseudo-classes: everything the document's own shape decides. PseudoRoot PseudoKind = iota PseudoEmpty PseudoFirstChild PseudoLastChild PseudoOnlyChild PseudoFirstOfType PseudoLastOfType PseudoOnlyOfType PseudoNthChild PseudoNthLastChild PseudoNthOfType PseudoNthLastOfType // Logical combinations. PseudoNot PseudoIs PseudoWhere // PseudoLang is :lang(), which reads the document's own language // declaration. PseudoLang // PseudoAnyLink is :link and :any-link, both of which mean "an element with // an href" once :visited cannot be true. PseudoAnyLink )
type Rule ¶
type Rule struct {
// At reports whether this is an at-rule. It is a field rather than a test
// on Name because it is the one thing that genuinely separates the two
// kinds, and reading it should not depend on knowing that a qualified
// rule's name is empty.
At bool
// Name is an at-rule's name without the "@" — "media", "page", "import".
// It is empty for a qualified rule.
Name string
// Prelude is everything before the block: an at-rule's parameters, or a
// qualified rule's selector list, still unparsed.
Prelude []ComponentValue
// Block is the contents of the {} block, and HasBlock says whether there
// was one. The two are separate because "@import url(x);" has no block and
// "@media print {}" has an empty one, and a caller has to tell them apart.
Block []ComponentValue
HasBlock bool
// Offset is the byte offset in the source at which the rule begins.
Offset int
}
A Rule is either a qualified rule — a style rule, whose prelude is a selector list — or an at-rule such as @media or @page.
The two are one type because the parser cannot tell what either means: at this layer a qualified rule is "some component values, then a {} block", and that is all that distinguishes it from an at-rule beginning with "@".
type Selector ¶
type Selector struct {
Compounds []Compound
// PseudoElement is "before", "after", "first-line", "first-letter" or
// "marker", empty when there is none. It is on the selector rather than on
// a compound because at most one may appear and only on the subject.
PseudoElement string
Specificity Specificity
// Offset is the byte offset in the source at which the selector begins.
Offset int
}
Selector is one complex selector: compound selectors joined by combinators.
The last compound is the *subject* — the element the selector selects. That matters more than it looks: matching runs right to left, from the subject outwards, because a document has far more elements than a selector has compounds and the subject is the cheapest thing to reject on.
type Specificity ¶
type Specificity struct{ A, B, C int }
Specificity is the (a, b, c) of Selectors Level 4 §17: identifiers, then classes and attributes and pseudo-classes, then element names and pseudo-elements. It decides which of two declarations wins when both apply.
func (Specificity) Less ¶
func (s Specificity) Less(other Specificity) bool
Less reports whether s loses to other. The three components are compared in order and do not carry: a thousand classes lose to one identifier, which is why this is not a single number.
func (Specificity) String ¶
func (s Specificity) String() string
type Token ¶
type Token struct {
Kind Kind
// Value is the token's text with escapes resolved and delimiters removed:
// the name of an Ident, Function, AtKeyword or Hash (without the "@" or
// "#"), the contents of a String or URL, or the single code point of a
// Delim.
Value string
// Unit is the unit of a Dimension: "px", "em", "deg". It is empty for every
// other kind.
Unit string
// Number is the value of a Number, Percentage or Dimension. A Percentage
// holds the number as written, so 50% is 50 rather than 0.5.
Number float64
// Repr is the number exactly as it was written, which is what serialising a
// stylesheet back out needs: 1.50 and 1.5 are the same value and not the
// same text.
Repr string
// IsInteger reports that a numeric token was written with no fractional
// part and no exponent. Some properties accept only integers, and 2.0 is
// not one of them.
IsInteger bool
// IsID reports that a Hash token's name is also a valid identifier. "#main"
// can be an ID selector and "#0f0" cannot, and the two are otherwise the
// same token.
IsID bool
// Offset is the byte offset in the original input at which this token
// begins, so that a diagnostic can point at the source.
Offset int
}
Token is one token of a CSS stylesheet.
Which fields carry meaning depends on Kind, and the zero value of a field is not distinguishable from an absent one — a Number token whose Value is empty is a token whose Kind was never Number. The accessors below are the safe way to read one.