css-what
Advanced tools
Comparing version 3.4.2 to 4.0.0
@@ -1,5 +0,16 @@ | ||
export default parse; | ||
export interface Options { | ||
/** | ||
* When false, tag names will not be lowercased. | ||
* @default true | ||
*/ | ||
lowerCaseAttributeNames?: boolean; | ||
/** | ||
* When false, attribute names will not be lowercased. | ||
* @default true | ||
*/ | ||
lowerCaseTags?: boolean; | ||
/** | ||
* When `true`, `xmlMode` implies both `lowerCaseTags` and `lowerCaseAttributeNames` are set to `false`. | ||
* @default false | ||
*/ | ||
xmlMode?: boolean; | ||
@@ -14,2 +25,3 @@ } | ||
ignoreCase: boolean; | ||
namespace: string | null; | ||
} | ||
@@ -29,5 +41,7 @@ declare type DataType = Selector[][] | null | string; | ||
name: string; | ||
namespace: string | null; | ||
} | ||
export interface UniversalSelector { | ||
type: "universal"; | ||
namespace: string | null; | ||
} | ||
@@ -39,3 +53,21 @@ export interface Traversal { | ||
export declare type TraversalType = "adjacent" | "child" | "descendant" | "parent" | "sibling"; | ||
declare function parse(selector: string, options?: Options): Selector[][]; | ||
/** | ||
* Checks whether a specific selector is a traversal. | ||
* This is useful eg. in swapping the order of elements that | ||
* are not traversals. | ||
* | ||
* @param selector Selector to check. | ||
*/ | ||
export declare function isTraversal(selector: Selector): selector is Traversal; | ||
/** | ||
* Parses `selector`, optionally with the passed `options`. | ||
* | ||
* @param selector Selector to parse. | ||
* @param options Options for parsing. | ||
* @returns Returns a two-dimensional array. | ||
* The first dimension represents selectors separated by commas (eg. `sub1, sub2`), | ||
* the second contains the relevant tokens for that selector. | ||
*/ | ||
export default function parse(selector: string, options?: Options): Selector[][]; | ||
export {}; | ||
//# sourceMappingURL=parse.d.ts.map |
201
lib/parse.js
"use strict"; | ||
var __spreadArrays = (this && this.__spreadArrays) || function () { | ||
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; | ||
for (var r = Array(s), k = 0, i = 0; i < il; i++) | ||
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) | ||
r[k] = a[j]; | ||
return r; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.default = parse; | ||
var reName = /^[^\\]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/; | ||
exports.isTraversal = void 0; | ||
var reName = /^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/; | ||
var reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi; | ||
// Modified version of https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L87 | ||
var reAttr = /^\s*((?:\\.|[\w\u00b0-\uFFFF-])+)\s*(?:(\S?)=\s*(?:(['"])((?:[^\\]|\\[^])*?)\3|(#?(?:\\.|[\w\u00b0-\uFFFF-])*)|)|)\s*(i)?\]/; | ||
var reAttr = /^\s*(?:(\*|[-\w]*)\|)?((?:\\.|[\w\u00b0-\uFFFF-])+)\s*(?:(\S?)=\s*(?:(['"])((?:[^\\]|\\[^])*?)\4|(#?(?:\\.|[\w\u00b0-\uFFFF-])*)|)|)\s*([iI])?\]/; | ||
var actionTypes = { | ||
@@ -37,2 +44,16 @@ undefined: "exists", | ||
]); | ||
var traversalNames = new Set(__spreadArrays([ | ||
"descendant" | ||
], Object.keys(Traversals).map(function (k) { return Traversals[k]; }))); | ||
/** | ||
* Checks whether a specific selector is a traversal. | ||
* This is useful eg. in swapping the order of elements that | ||
* are not traversals. | ||
* | ||
* @param selector Selector to check. | ||
*/ | ||
function isTraversal(selector) { | ||
return traversalNames.has(selector.type); | ||
} | ||
exports.isTraversal = isTraversal; | ||
var stripQuotesFromPseudos = new Set(["contains", "icontains"]); | ||
@@ -58,11 +79,21 @@ var quotes = new Set(['"', "'"]); | ||
} | ||
/** | ||
* Parses `selector`, optionally with the passed `options`. | ||
* | ||
* @param selector Selector to parse. | ||
* @param options Options for parsing. | ||
* @returns Returns a two-dimensional array. | ||
* The first dimension represents selectors separated by commas (eg. `sub1, sub2`), | ||
* the second contains the relevant tokens for that selector. | ||
*/ | ||
function parse(selector, options) { | ||
var subselects = []; | ||
selector = parseSelector(subselects, "" + selector, options); | ||
if (selector !== "") { | ||
throw new Error("Unmatched selector: " + selector); | ||
var endIndex = parseSelector(subselects, "" + selector, options, 0); | ||
if (endIndex < selector.length) { | ||
throw new Error("Unmatched selector: " + selector.slice(endIndex)); | ||
} | ||
return subselects; | ||
} | ||
function parseSelector(subselects, selector, options) { | ||
exports.default = parse; | ||
function parseSelector(subselects, selector, options, selectorIndex) { | ||
var _a, _b; | ||
@@ -72,15 +103,15 @@ if (options === void 0) { options = {}; } | ||
var sawWS = false; | ||
function getName() { | ||
var match = selector.match(reName); | ||
function getName(offset) { | ||
var match = selector.slice(selectorIndex + offset).match(reName); | ||
if (!match) { | ||
throw new Error("Expected name, found " + selector); | ||
throw new Error("Expected name, found " + selector.slice(selectorIndex)); | ||
} | ||
var sub = match[0]; | ||
selector = selector.substr(sub.length); | ||
return unescapeCSS(sub); | ||
var name = match[0]; | ||
selectorIndex += offset + name.length; | ||
return unescapeCSS(name); | ||
} | ||
function stripWhitespace(start) { | ||
while (isWhitespace(selector.charAt(start))) | ||
start++; | ||
selector = selector.substr(start); | ||
function stripWhitespace(offset) { | ||
while (isWhitespace(selector.charAt(selectorIndex + offset))) | ||
offset++; | ||
selectorIndex += offset; | ||
} | ||
@@ -93,5 +124,10 @@ function isEscaped(pos) { | ||
} | ||
function ensureNotTraversal() { | ||
if (tokens.length > 0 && isTraversal(tokens[tokens.length - 1])) { | ||
throw new Error("Did not expect successive traversals."); | ||
} | ||
} | ||
stripWhitespace(0); | ||
while (selector !== "") { | ||
var firstChar = selector.charAt(0); | ||
var firstChar = selector.charAt(selectorIndex); | ||
if (isWhitespace(firstChar)) { | ||
@@ -102,2 +138,3 @@ sawWS = true; | ||
else if (firstChar in Traversals) { | ||
ensureNotTraversal(); | ||
tokens.push({ type: Traversals[firstChar] }); | ||
@@ -118,14 +155,8 @@ sawWS = false; | ||
if (sawWS) { | ||
if (tokens.length > 0) { | ||
tokens.push({ type: "descendant" }); | ||
} | ||
ensureNotTraversal(); | ||
tokens.push({ type: "descendant" }); | ||
sawWS = false; | ||
} | ||
if (firstChar === "*") { | ||
selector = selector.substr(1); | ||
tokens.push({ type: "universal" }); | ||
} | ||
else if (firstChar in attribSelectors) { | ||
if (firstChar in attribSelectors) { | ||
var _c = attribSelectors[firstChar], name_1 = _c[0], action = _c[1]; | ||
selector = selector.substr(1); | ||
tokens.push({ | ||
@@ -135,14 +166,16 @@ type: "attribute", | ||
action: action, | ||
value: getName(), | ||
value: getName(1), | ||
ignoreCase: false, | ||
namespace: null, | ||
}); | ||
} | ||
else if (firstChar === "[") { | ||
selector = selector.substr(1); | ||
var attributeMatch = selector.match(reAttr); | ||
var attributeMatch = selector | ||
.slice(selectorIndex + 1) | ||
.match(reAttr); | ||
if (!attributeMatch) { | ||
throw new Error("Malformed attribute selector: " + selector); | ||
throw new Error("Malformed attribute selector: " + selector.slice(selectorIndex)); | ||
} | ||
var completeSelector = attributeMatch[0], baseName = attributeMatch[1], actionType = attributeMatch[2], _d = attributeMatch[4], quotedValue = _d === void 0 ? "" : _d, _e = attributeMatch[5], value = _e === void 0 ? quotedValue : _e, ignoreCase = attributeMatch[6]; | ||
selector = selector.substr(completeSelector.length); | ||
var completeSelector = attributeMatch[0], _d = attributeMatch[1], namespace = _d === void 0 ? null : _d, baseName = attributeMatch[2], actionType = attributeMatch[3], _e = attributeMatch[5], quotedValue = _e === void 0 ? "" : _e, _f = attributeMatch[6], value = _f === void 0 ? quotedValue : _f, ignoreCase = attributeMatch[7]; | ||
selectorIndex += completeSelector.length + 1; | ||
var name_2 = unescapeCSS(baseName); | ||
@@ -157,2 +190,3 @@ if ((_a = options.lowerCaseAttributeNames) !== null && _a !== void 0 ? _a : !options.xmlMode) { | ||
value: unescapeCSS(value), | ||
namespace: namespace, | ||
ignoreCase: !!ignoreCase, | ||
@@ -162,43 +196,34 @@ }); | ||
else if (firstChar === ":") { | ||
if (selector.charAt(1) === ":") { | ||
selector = selector.substr(2); | ||
if (selector.charAt(selectorIndex + 1) === ":") { | ||
tokens.push({ | ||
type: "pseudo-element", | ||
name: getName().toLowerCase(), | ||
name: getName(2).toLowerCase(), | ||
}); | ||
continue; | ||
} | ||
selector = selector.substr(1); | ||
var name_3 = getName().toLowerCase(); | ||
var name_3 = getName(1).toLowerCase(); | ||
var data = null; | ||
if (selector.startsWith("(")) { | ||
if (selector.charAt(selectorIndex) === "(") { | ||
if (unpackPseudos.has(name_3)) { | ||
var quot = selector.charAt(1); | ||
var quoted = quotes.has(quot); | ||
selector = selector.substr(quoted ? 2 : 1); | ||
if (quotes.has(selector.charAt(selectorIndex + 1))) { | ||
throw new Error("Pseudo-selector " + name_3 + " cannot be quoted"); | ||
} | ||
data = []; | ||
selector = parseSelector(data, selector, options); | ||
if (quoted) { | ||
if (!selector.startsWith(quot)) { | ||
throw new Error("Unmatched quotes in :" + name_3); | ||
} | ||
else { | ||
selector = selector.substr(1); | ||
} | ||
} | ||
if (!selector.startsWith(")")) { | ||
selectorIndex = parseSelector(data, selector, options, selectorIndex + 1); | ||
if (selector.charAt(selectorIndex) !== ")") { | ||
throw new Error("Missing closing parenthesis in :" + name_3 + " (" + selector + ")"); | ||
} | ||
selector = selector.substr(1); | ||
selectorIndex += 1; | ||
} | ||
else { | ||
var pos = 1; | ||
selectorIndex += 1; | ||
var start = selectorIndex; | ||
var counter = 1; | ||
for (; counter > 0 && pos < selector.length; pos++) { | ||
if (selector.charAt(pos) === "(" && | ||
!isEscaped(pos)) { | ||
for (; counter > 0 && selectorIndex < selector.length; selectorIndex++) { | ||
if (selector.charAt(selectorIndex) === "(" && | ||
!isEscaped(selectorIndex)) { | ||
counter++; | ||
} | ||
else if (selector.charAt(pos) === ")" && | ||
!isEscaped(pos)) { | ||
else if (selector.charAt(selectorIndex) === ")" && | ||
!isEscaped(selectorIndex)) { | ||
counter--; | ||
@@ -210,4 +235,3 @@ } | ||
} | ||
data = selector.substr(1, pos - 2); | ||
selector = selector.substr(pos); | ||
data = selector.slice(start, selectorIndex - 1); | ||
if (stripQuotesFromPseudos.has(name_3)) { | ||
@@ -224,16 +248,45 @@ var quot = data.charAt(0); | ||
} | ||
else if (reName.test(selector)) { | ||
var name_4 = getName(); | ||
if ((_b = options.lowerCaseTags) !== null && _b !== void 0 ? _b : !options.xmlMode) { | ||
name_4 = name_4.toLowerCase(); | ||
} | ||
tokens.push({ type: "tag", name: name_4 }); | ||
} | ||
else { | ||
if (tokens.length && | ||
tokens[tokens.length - 1].type === "descendant") { | ||
tokens.pop(); | ||
var namespace = null; | ||
var name_4 = void 0; | ||
if (firstChar === "*") { | ||
selectorIndex += 1; | ||
name_4 = "*"; | ||
} | ||
addToken(subselects, tokens); | ||
return selector; | ||
else if (reName.test(selector.slice(selectorIndex))) { | ||
name_4 = getName(0); | ||
} | ||
else { | ||
/* | ||
* We have finished parsing the selector. | ||
* Remove descendant tokens at the end if they exist, | ||
* and return the last index, so that parsing can be | ||
* picked up from here. | ||
*/ | ||
if (tokens.length && | ||
tokens[tokens.length - 1].type === "descendant") { | ||
tokens.pop(); | ||
} | ||
addToken(subselects, tokens); | ||
return selectorIndex; | ||
} | ||
if (selector.charAt(selectorIndex) === "|") { | ||
namespace = name_4; | ||
if (selector.charAt(selectorIndex + 1) === "*") { | ||
name_4 = "*"; | ||
selectorIndex += 2; | ||
} | ||
else { | ||
name_4 = getName(1); | ||
} | ||
} | ||
if (name_4 === "*") { | ||
tokens.push({ type: "universal", namespace: namespace }); | ||
} | ||
else { | ||
if ((_b = options.lowerCaseTags) !== null && _b !== void 0 ? _b : !options.xmlMode) { | ||
name_4 = name_4.toLowerCase(); | ||
} | ||
tokens.push({ type: "tag", name: name_4, namespace: namespace }); | ||
} | ||
} | ||
@@ -243,3 +296,3 @@ } | ||
addToken(subselects, tokens); | ||
return selector; | ||
return selectorIndex; | ||
} | ||
@@ -246,0 +299,0 @@ function addToken(subselects, tokens) { |
import { Selector } from "./parse"; | ||
export default function stringify(token: Selector[][]): string; | ||
/** | ||
* Turns `selector` back into a string. | ||
* | ||
* @param selector Selector to stringify. | ||
*/ | ||
export default function stringify(selector: Selector[][]): string; | ||
//# sourceMappingURL=stringify.d.ts.map |
@@ -27,5 +27,12 @@ "use strict"; | ||
"\\", | ||
"(", | ||
")", | ||
])); | ||
function stringify(token) { | ||
return token.map(stringifySubselector).join(", "); | ||
/** | ||
* Turns `selector` back into a string. | ||
* | ||
* @param selector Selector to stringify. | ||
*/ | ||
function stringify(selector) { | ||
return selector.map(stringifySubselector).join(", "); | ||
} | ||
@@ -50,5 +57,5 @@ exports.default = stringify; | ||
case "universal": | ||
return "*"; | ||
return getNamespace(token.namespace) + "*"; | ||
case "tag": | ||
return escapeName(token.name); | ||
return getNamespacedName(token); | ||
case "pseudo-element": | ||
@@ -60,12 +67,10 @@ return "::" + escapeName(token.name); | ||
if (typeof token.data === "string") { | ||
return ":" + escapeName(token.name) + "(" + token.data + ")"; | ||
return ":" + escapeName(token.name) + "(" + escapeName(token.data) + ")"; | ||
} | ||
return ":" + escapeName(token.name) + "(" + stringify(token.data) + ")"; | ||
case "attribute": | ||
if (token.action === "exists") { | ||
return "[" + escapeName(token.name) + "]"; | ||
} | ||
case "attribute": { | ||
if (token.name === "id" && | ||
token.action === "equals" && | ||
!token.ignoreCase) { | ||
!token.ignoreCase && | ||
!token.namespace) { | ||
return "#" + escapeName(token.value); | ||
@@ -75,8 +80,22 @@ } | ||
token.action === "element" && | ||
!token.ignoreCase) { | ||
!token.ignoreCase && | ||
!token.namespace) { | ||
return "." + escapeName(token.value); | ||
} | ||
return "[" + escapeName(token.name) + actionTypes[token.action] + "='" + escapeName(token.value) + "'" + (token.ignoreCase ? "i" : "") + "]"; | ||
var name_1 = getNamespacedName(token); | ||
if (token.action === "exists") { | ||
return "[" + name_1 + "]"; | ||
} | ||
return "[" + name_1 + actionTypes[token.action] + "='" + escapeName(token.value) + "'" + (token.ignoreCase ? "i" : "") + "]"; | ||
} | ||
} | ||
} | ||
function getNamespacedName(token) { | ||
return "" + getNamespace(token.namespace) + escapeName(token.name); | ||
} | ||
function getNamespace(namespace) { | ||
return namespace | ||
? (namespace === "*" ? "*" : escapeName(namespace)) + "|" | ||
: ""; | ||
} | ||
function escapeName(str) { | ||
@@ -83,0 +102,0 @@ return str |
@@ -5,3 +5,3 @@ { | ||
"description": "a CSS selector parser", | ||
"version": "3.4.2", | ||
"version": "4.0.0", | ||
"funding": "https://github.com/sponsors/fb55", | ||
@@ -8,0 +8,0 @@ "repository": { |
@@ -28,7 +28,7 @@ # css-what [![Build Status](https://secure.travis-ci.org/fb55/css-what.svg?branch=master)](http://travis-ci.org/fb55/css-what) | ||
**`CSSwhat.parse(str, options)` - Parses `str`, optionally with the passed `options`.** | ||
**`CSSwhat.parse(selector, options)` - Parses `selector`, optionally with the passed `options`.** | ||
The function returns a two-dimensional array. The first array represents selectors separated by commas (eg. `sub1, sub2`), the second contains the relevant tokens for that selector. Possible token types are: | ||
| name | attributes | example | output | | ||
| name | properties | example | output | | ||
| ---------------- | --------------------------------------- | ------------- | ---------------------------------------------------------------------------------------- | | ||
@@ -35,0 +35,0 @@ | `tag` | `name` | `div` | `{ type: 'tag', name: 'div' }` | |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
27927
495