/css-selector-inspector

🔎 Parse, validate, and inspect CSS selectors

Primary LanguageJavaScript

🔎 css-selector-inspector (CSI)

CSI is a complete spec-based CSS3 selector parser and utility written in JavaScript.

Features:

  • Parse and tokenize any valid CSS3 selector string, guaranteed!
    • Supports escaped characters, including escaped unicode code points
    • Properly handles all valid whitespace
    • Parses around comment blocks
  • Validate that a selector is well-formed
  • Calculate specificity level of each compound selector
  • Sort theoretical property declarations in proper cascading order based on selector specificity, origin (user or author stylesheet or inline), and !important directive
  • Normalize selector strings by parsing and reassembling the components in a deterministic manner
  • Escape raw values to use as CSS identifiers or strings

Table of Contents

Usage

Install CSI into your project:

npm install css-selector-inspector --save
import CSI from 'css-selector-inspector';

console.log(CSI.parse('div.content:last-child > p:not([aria-hidden])::before'));
/* output:
[
  {
    tokens: [
      {
        type: "typeSelector",
        namespace: null,
        name: "div",
        location: 0,
        raw: "div",
        specificityType: "d"
      },
      {
        type: "classSelector",
        name: "content",
        location: 3,
        raw: ".content",
        specificityType: "c"
      },
      {
        type: "pseudoClassSelector",
        name: "last-child",
        location: 11,
        raw: ":last-child",
        specificityType: "c",
        expression: null
      },
      {
        type: "childCombinator",
        location: 22,
        raw: " > ",
        specificityType: null
      },
      {
        type: "typeSelector",
        namespace: null,
        name: "p",
        location: 25,
        raw: "p",
        specificityType: "d"
      },
      {
        type: "negationSelector",
        tokens: [
          {
            namespace: null,
            name: "aria-hidden",
            location: 31,
            raw: "[aria-hidden]",
            specificityType: "c",
            type: "attributePresenceSelector"
          }
        ],
        location: 26,
        raw: ":not([aria-hidden])",
        specificityType: null
      },
      {
        name: "before",
        location: 45,
        raw: "::before",
        type: "pseudoElementSelector",
        specificityType: "d"
      }
    ],
    specificity: {
      a: 0,
      b: 0,
      c: 3,
      d: 3
    }
  }
]
 */

console.log(CSI.parse('html, /* comments are ok! */ *|body'));
/* output:
[
  {
    tokens: [
      {
        type: "typeSelector",
        namespace: null,
        name: "html",
        location: 0,
        raw: "html",
        specificityType: "d"
      }
    ],
    specificity: {
      a: 0,
      b: 0,
      c: 0,
      d: 1
    }
  },
  {
    tokens: [
      {
        type: "typeSelector",
        namespace: {
          type: "wildcard"
        },
        name: "body",
        location: 29,
        raw: "body",
        specificityType: "d"
      }
    ],
    specificity: {
      a: 0,
      b: 0,
      c: 0,
      d: 1
    }
  }
]

Objects

CSI

This is the main object containing static methods for most of the selector utilities.

Static Methods

parse()

Parse a string containing selectors (one or more separated by commas) into its component tokens.

Params

  • string selector - selector list string, excluding braces {}

Returns

array an array of Selector objects (even if there is only one selector)

Throws

  • Syntax error if the string cannot be parsed, usually providing the location of the first invalid portion
  • If the parsing results in multiple, differing results, an ambiguity error is thrown (please file an issue if this error is encountered)

isValid()

Determine if a selector string is syntactically valid. This is a convenience method if you don't care about the parsed result, and it will not throw an error if passed an invalid selector. This will not determine if the components are supported by a CSS implementation (e.g. if a particular pseudo class actually exists in any browser).

Params

  • string selector - selector list string, excluding braces {}

Returns

bool determination of whether the selector string appears to be syntactically valid


normalize()

Normalize a selector string by parsing and reassembling. Removes extraneous whitespace, unencodes unnecessary unicode escapes, removes comments, and normalizes nth formulas.

Params

  • string selector - selector list string, excluding braces {}

Returns

string normalized selector string

Throws

  • Uses parse(), so has the potential to throw errors due to invalid selector strings.

sort()

Sort selectors and/or theoretical properties based on the cascading precedence order. All arguments are internally converted to TheoreticalProperty objects for comparison purposes but are returned as the original passed object(s). Selectors/properties are sorted in precedence order from highest to lowest, meaning the first item in the resulting array is the property that would win out. For items that are otherwise equal based on precedence and specificity, the item passed latest will appear first in the resulting array.

Params

  • array testObjects | object ...testObject - Selector or TheoreticalProperty objects or plain objects to be casted as TheoreticalProperty objects (can be passed as a single array of objects or as multiple object arguments)

Returns

array original passed objects in proper cascade order (does not mutate or return the original array if passed)

Throws

  • TheoreticalProperty objects may throw errors if invalid values are passed. See its documentation for more details.

escape()

Escape a JS value to use as a CSS identity. This is an implementation of the CSS spec which defines the CSS.escape() method, available in many browsers.

Params

  • mixed ident - raw value (will be casted as a string) to use as a CSS identity

Returns

string escaped identity that can be safely used to compose a CSS selector string


escapeString()

Escape a JS value to use as a CSS string. While escape() can be used for this purpose as well, it escapes more characters than necessary. This method only acts upon characters that must be escaped for use as a string, and returns the value with surronding double quotes.

Params

  • mixed string - raw value (will be casted as a string) to use as a CSS string

Returns

string escaped string, including surrounding double quote characters, that can be safely used to compose a CSS selector string


Selector

The Selector object is returned as the result of parsing a selector string, which gives access to the token data and specificity level. Selector objects must be instantiated with a token array, so such objects typically result from calling parse() rather than direct instantiation.

Constructor

  • array tokens - token objects

Properties

specificity

object contains properties a, b, c, and d, each with a count of the corresponding selectors of each specificity type


tokens

array array of token objects that comprise the compound selector, including combinators

Combinator Token Types

All combinator token objects have the following properties:

  • string type - the type of token
  • int location - the index in the original string where the token was located
  • string raw - the raw string that was parsed into the token
Type Example Additional Properties
adjacentSiblingCombinator sibling + nextsibling
  • null specificityType
childCombinator parent > child
  • null specificityType
descendantCombinator ancestor descendant
  • null specificityType
generalSiblingCombinator sibling ~ sibling
  • null specificityType

Simple Selector Token Types

All simple selector token objects have the following properties:

  • string type - the type of token
  • int location - the index in the original string where the token was located
  • string raw - the raw string that was parsed into the token
Type Example Additional Properties
attributePresenceSelector [attr]
  • string name - attribute name
  • string | object | null namespace - namespace string, if provided; object with type: 'wildcard' if namespace is *; null if no namespace
  • string specificityType - c
attributeValueSelector [attr="value"]
  • string name - attribute name
  • string value - attribute value
  • string operator - value comparison operator, one of: =, ~=, |=, ^=, $=, *=
  • string | object | null namespace - namespace string, if provided; object with type: 'wildcard' if namespace is *; null if no namespace
  • string specificityType - c
classSelector .class
  • string name - class name
  • string specificityType - c
idSelector #id
  • string name - ID
  • string specificityType - b
negationSelector :not(.class)
  • array tokens - simple selector token objects
  • null specificityType
pseudoClassSelector :pseudo-class(expression)
  • string name - pseudo element name
  • object | null expression - optional parenthetical expression as a data token object
  • string specificityType - c
pseudoElementSelector ::pseudo-element
  • string name - pseudo element name
  • string specificityType - d
typeSelector element
  • string name - element name
  • string | object | null namespace - namespace string, if provided; object with type: 'wildcard' if namespace is *; null if no namespace
  • string specificityType - d
universalSelector *
  • string | object | null namespace - namespace string, if provided; object with type: 'wildcard' if namespace is *; null if no namespace
  • null specificityType

Data Token Types

All data token objects have the following properties:

  • string type - the type of token
  • string raw - the raw string that was parsed into the token
Type Example Additional Properties
identity identity
  • string parsed - identity name
nthFormula 3n+4
  • object parsed - object with properties a and b with their respective integer values from the an+b formula
nthKeyword even
  • string parsed - even or odd
string "string"
  • string parsed - value of string

Methods

toString()

Returns

string properly formed selector string


TheoreticalProperty

TheoreticalProperty objects are used primarily to compare and sort different selectors. They are "theoretical" properties because they are not concerned with any property name or value, but rather the source of the property (e.g. user or author stylesheet or inline) and whether the !important directive is set, all which contribute to determining the cascading order of precedence of defined properties for an element.

The easiest way to use these objects is to not use them at all! Instead, you can pass Selector objects or plain objects to CSI.sort() and the TheoreticalProperty objects will be created under the hood for the comparison. However, it is more efficient to create and reuse TheoreticalProperty objects when doing multiple/repeated comparisons.

Constructor

  • object options | Selector selector | undefined - the constructor accepts a plain object which sets corresponding properties on the TheoreticalProperty object; as a shortcut, a Selector object may be passed instead, setting the selector property to that object and the origin property to "author"; if no argument is passed, the default properties are used, which make the object a non-important inline style

Properties

important

bool whether the property was defined with the !important directive (properties originating from a userAgent should not ever be important and will throw an error if used in a comparison)

Default: false


origin

string where the CSS property originates from, one of: user (configured in the user's browser settings), userAgent (defined by the browser itself), author (defined in an external stylesheet or <style> block), or inline (defined in an element's style="" attribute)

Default: "inline"


selector

Selector | null for CSS properties not originating from an inline style attribute, this should be set to a Selector object, otherwise this should be null (if the expected value does not match the defined origin, an error will be thrown upon comparison)

Default: null

Methods

getSpecificity()

Get the specificity of the selector/property. If the property's origin is a selector, the specificity object comes directly from the Selector object, otherwise it is always {a: 1, b: 0, c: 0, d: 0} for inline styles.

Returns

object plain object with properties a, b, c, d, based on how specificity is tallied

Throws

  • An error is thrown if the origin is invalid or does not match the expected value of selector

getPrecedenceLevel()

Based on the origin of the property and whether it is !important, determine the precendence level. A lower value indicates a higher precedence.

Returns

int precedence value, lower being higher precedence

Throws

  • An error is thrown if the origin type is invalid.

Notes

  • This package uses a number methods introduced by ECMAScript 2015 which may not be available in all environments. You may need to use a polyfill for the following methods:
  • The parsing functionality is built upon nearley, a JS implementation of the Earley Parsing Algorithm. I was introduced to nearley and inspired to use it for the purposes of CSS selector parsing by scalpel.
  • After evaluating several CSS selector parsers, my goal was to produce one that could handle any valid selector string based on the spec. As such, please file an issue if you come across any valid selector strings that cannot be parsed!