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 one selector token at an exact index
- Inspect the source ranges of extracted 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, and decodeCssSelector() below decodes one extracted selector when needed.
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 — version
You can import version:
API — types
This package is written in TypeScript and exports the following types:
| Type | Description |
|---|---|
ResultType: Result | |
Result | What extract() returns — res and ranges. |
CssSelectorTokenType: CssSelectorToken | |
CssSelectorToken | What readCssSelectorToken() returns — value, raw and range. |
import type { CssSelectorToken, 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. 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)),
);