No 3rd party dependencies. All dependencies and devDependencies, checked recursively, are Codsen packages.
Permalink to InstallationInstallation
Permalink to Quick TakeQuick Take
Permalink to ExamplesExamples
- Extract selectors from HTML-style attribute selectors
- Non-parsing algorithm can tackle really dodgy CSS
- Decode CSS selector escapes
- Extract raw escaped selectors
- Isolate a selector prelude before extraction
- Read lexical tokens from an isolated CSS region, retaining original offsets.
- Read one selector token at an exact index
- Inspect the source ranges of extracted selectors
- Read semantic attribute selectors
- Processes whole CSS selectors
API — extract()
The main function extract() is imported like this:
It’s a function which takes one input argument:
| Input argument | Type | Obligatory | Description |
|---|---|---|---|
strType: String Obligatory: yes | |||
str | String | yes | An isolated selector prelude or already-tokenized selector fragment |
The function will return a plain object (a Result type above):
| Key in a returned object | Type | Description |
|---|---|---|
resType: Array of strings | ||
res | Array of strings | Raw class and ID selector spellings |
rangesType: Array of ranges or null (compatible with Ranges) | ||
ranges | Array of ranges or null (compatible with Ranges) | Locations of extracted strings |
Accepted input
extract() is a context-free scanner. Pass an isolated CSS selector prelude or an already-tokenized selector fragment, not a complete stylesheet or HTML document.
The function does not recognise declarations, comments, strings outside attribute values, URLs, or HTML character references as outer syntax. If those constructs are included in the input, their dots and hashes can be returned as apparent selectors. For example, a colour such as #fff, a URL fragment such as #icon, or a quoted string such as ".example" can all look like selector tokens to a context-free scan.
Let a CSS parser isolate the selector prelude before calling extract(). The slice below is only a short illustration for a single flat rule:
import { extract } from "string-extract-class-names";
const rule =
'.real { color: #fff; background: url("/icon.svg#mark"); content: ".not-a-selector"; }';
const selectorPrelude = rule.slice(0, rule.indexOf("{"));
console.log(extract(selectorPrelude).res);
// => [".real"]
HTML character-reference decoding belongs to the HTML parsing layer. Decode references there before constructing selector input; extract() does not turn a literal spelling such as © into a character. CSS escapes are a separate CSS feature: extract() preserves their raw spelling. Use decodeCssSelector() for identifier spellings, or extractCssSelectorTokens() for canonical values that also account for quoted CSS strings and string continuations.
API — extractCssSelectorTokens()
Use this function to compare class and ID names while keeping the original spelling and source ranges:
function extractCssSelectorTokens(str: string): CssSelectorToken[];
It accepts the same selector fragments as extract() and returns an empty
array when it finds no names. Each entry contains a canonical value, the
original raw source slice, and its UTF-16 range with an exclusive end.
The value starts with . for a class or # for an ID. For an attribute
selector, this prefix is synthetic and is absent from raw and the range.
import { extractCssSelectorTokens } from "string-extract-class-names";
const tokens = extractCssSelectorTokens(String.raw`[cl\61 ss="a,b"]`);
console.log(tokens);
// => [{ value: ".a,b", raw: "a,b", range: [11, 14] }]
Attribute names are CSS identifiers: [cl\61 ss=foo] has the same meaning
as [class=foo]. Quoted values are CSS strings, so numeric values,
punctuation, and backslash-newline continuations are accepted. For example,
[class="123"] identifies class 123, and a continuation between a and
b identifies class ab while retaining the continuation in raw.
Exact class values produce individual HTML class tokens. A [class~=value]
value containing HTML ASCII whitespace produces no tokens. Exact ID values
remain complete strings: [id=" x "] produces value: "# x ". For
compatibility, extract() retains its older trimming behavior for this ID
form and returns "#x".
These entries are an inventory, not a selector evaluator. In particular,
[class="a b"] still compares the complete attribute value in CSS; extracting
.a and .b does not establish that the selector matches an element.
The helper does not HTML-decode character references or parse an entire
stylesheet. Invalid non-string input throws a TypeError.
API — readCssSelectorToken()
The function readCssSelectorToken() is imported like this:
Reads a single class or ID selector starting at an exact index. Use it when you’re already walking a string and know where a . or # sits — extract() scans a whole string, while this function reads one token:
| Input argument | Type | Obligatory | Description |
|---|---|---|---|
strType: String Obligatory: yes | |||
str | String | yes | A string to read from. |
startType: Natural number or zero Obligatory: yes | |||
start | Natural number or zero | yes | Index of the leading . or #. |
It returns a plain object (a CssSelectorToken type), or null if start doesn’t point at a . or #:
| Key | Type | Description |
|---|---|---|
valueType: String | ||
value | String | The selector with any CSS escapes decoded. |
rawType: String | ||
raw | String | The selector exactly as it appears in the source. |
rangeType: Array of two numbers | ||
range | Array of two numbers | Where the token starts and ends in str. |
import { readCssSelectorToken } from "string-extract-class-names";
console.log(readCssSelectorToken(".a-b .c", 0));
// => { value: ".a-b", raw: ".a-b", range: [0, 4] }
console.log(readCssSelectorToken(".a-b .c", 2));
// => null
API — decodeCssSelector()
The function decodeCssSelector() is imported like this:
Decodes the CSS escapes in one already-extracted class or ID selector, keeping its leading dot or hash:
Utility-first CSS frameworks lean on escapes heavily — .md\:w-1\/2 is one class name, not three tokens. This function turns the source form into the class name a browser sees:
import { decodeCssSelector } from "string-extract-class-names";
console.log(decodeCssSelector(".a\\:b"));
// => ".a:b"
console.log(decodeCssSelector(".plain"));
// => ".plain"
It’s what readCssSelectorToken() uses to fill the value key.
API — readCssToken()
Reads one lexical token at an exact UTF-16 index in an isolated CSS region. Advance to range[1] to read the next token. An index outside the string returns null.
readCssToken(str: string, start: number): CssToken | null;
import { readCssToken } from "string-extract-class-names";
readCssToken(String.raw`@m\65 dia all`, 0);
// => {
// kind: "at-keyword",
// value: "media",
// raw: String.raw`@m\65 dia`,
// range: [0, 9],
// }
Each token has kind, value, raw, and range keys. raw is the original source slice, and range contains its start and exclusive end indexes. The reader recognizes identifiers, at-keywords, functions, strings, bad strings, unquoted URLs, bad URLs, comments, whitespace, and delimiters.
Identifier, at-keyword, function, string, and URL values are CSS-decoded. At-keyword values omit @; function values omit (; string and URL values omit their surrounding syntax. Whitespace values keep their raw spelling. Comment values contain the raw comment body. Bad strings expose their decoded prefix, and bad URLs have an empty value. Quoted URL arguments produce separate function, whitespace, and string tokens.
This is a bounded lexical reader, not a complete CSS tokenizer or parser. Numbers, hashes, and other grammar-specific tokens use individual delimiters and identifiers. Its token sequence alone cannot determine whether removing a separator preserves CSS meaning.
Isolate CSS using HTML boundaries before calling this helper. For an inline style attribute, decode HTML character references first and retain a mapping to the original HTML if you need source ranges. Style-element text is already CSS and must not be HTML-decoded.
API — version
You can import version:
API — types
This package is written in TypeScript and exports the following types:
| Type | Description |
|---|---|
CssTokenType: CssToken | |
CssToken | A lexical token returned by readCssToken() — kind, value, raw, and range. |
ResultType: Result | |
Result | What extract() returns — res and ranges. |
CssSelectorTokenType: CssSelectorToken | |
CssSelectorToken | A token returned by readCssSelectorToken() or extractCssSelectorTokens() — value, raw, and range. |
import type { CssSelectorToken, CssToken, Result } from "string-extract-class-names";
The use
The email-comb library uses string-extract-class-names after it has isolated selector heads from a stylesheet. It then compares those extracted class and ID names with the email’s HTML while detecting and deleting unused CSS styles.
Bracket notation
Normally, class and ID selectors in CSS include dots or hashes, for example, div.first-class.second-class. The ranges include those dots and hashes:
import { extract } from "string-extract-class-names";
const res = extract("div.first-class.second-class");
console.log(res);
// => {
// res: [".first-class", ".second-class"],
// ranges: [
// [3, 15],
// [15, 28],
// ],
// }
Dots above are at indexes 3 and 15.
The function also supports the exact [class=value], whitespace-list [class~=value], and exact [id=value] attribute-selector forms. Attribute names are ASCII-case-insensitive after CSS escape decoding. Values can use CSS identifier or string syntax, and an exact class string can contain multiple HTML class tokens. Partial-match operators such as ^=, $=, and *= do not produce selectors.
For example, instead of td.croodles, CSS can use td[class="croodles"]. This notation was historically useful in email templates because of Yahoo rendering behaviour:
import { extract } from "string-extract-class-names";
const str = `td[id=" abc-def "]`;
const res = extract(str);
console.log(res);
// => {
// res: ["#abc-def"],
// ranges: [[8, 15]],
// }
// However:
console.log(str.slice(8, 15));
// => "abc-def"
// There is no hash in the source slice because the attribute value did not
// contain one. The hash in `res` is synthetic.
For dot and hash selectors that appear literally in the source, res matches the corresponding String.prototype.slice() calls:
assert.deepEqual(
res,
ranges.map(([from, to]) => str.slice(from, to)),
);