puppeteer/packages/puppeteer-core/src/injected/PSelectorParser.ts

138 lines
4.0 KiB
TypeScript
Raw Normal View History

2023-02-15 18:42:32 +00:00
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
2023-03-14 09:13:23 +00:00
import {Token, tokenize, TokenType} from './PSelectorTokenizer.js';
2023-02-15 18:42:32 +00:00
export type CSSSelector = string;
export type PPseudoSelector = {
name: string;
value: string;
};
export const enum PCombinator {
Descendent = '>>>',
Child = '>>>>',
}
export type CompoundPSelector = Array<CSSSelector | PPseudoSelector>;
export type ComplexPSelector = Array<CompoundPSelector | PCombinator>;
export type ComplexPSelectorList = ComplexPSelector[];
2023-02-15 18:42:32 +00:00
class TokenSpan {
2023-03-14 09:13:23 +00:00
#tokens: Token[] = [];
#selector: string;
2023-02-15 18:42:32 +00:00
constructor(selector: string) {
this.#selector = selector;
}
2023-02-15 18:42:32 +00:00
get length(): number {
return this.#tokens.length;
2023-02-15 18:42:32 +00:00
}
2023-03-14 09:13:23 +00:00
add(token: Token) {
this.#tokens.push(token);
2023-02-15 18:42:32 +00:00
}
toStringAndClear() {
2023-03-14 09:13:23 +00:00
const startToken = this.#tokens[0] as Token;
const endToken = this.#tokens[this.#tokens.length - 1] as Token;
this.#tokens.splice(0);
return this.#selector.slice(startToken.pos[0], endToken.pos[1]);
2023-02-15 18:42:32 +00:00
}
}
2023-02-15 18:42:32 +00:00
const ESCAPE_REGEXP = /\\[\s\S]/g;
const unquote = (text: string): string => {
if (text.length > 1) {
for (const char of ['"', "'"]) {
if (!text.startsWith(char) || !text.endsWith(char)) {
continue;
2023-02-15 18:42:32 +00:00
}
return text
.slice(char.length, -char.length)
.replace(ESCAPE_REGEXP, match => {
return match.slice(1);
});
2023-02-15 18:42:32 +00:00
}
}
return text;
};
2023-02-15 18:42:32 +00:00
export function parsePSelectors(
selector: string
): [selector: ComplexPSelectorList, isPureCSS: boolean] {
let isPureCSS = true;
const tokens = tokenize(selector);
if (tokens.length === 0) {
return [[], isPureCSS];
2023-02-15 18:42:32 +00:00
}
let compoundSelector: CompoundPSelector = [];
let complexSelector: ComplexPSelector = [compoundSelector];
const selectors: ComplexPSelectorList = [complexSelector];
const storage = new TokenSpan(selector);
for (const token of tokens) {
switch (token.type) {
2023-03-14 09:13:23 +00:00
case TokenType.Combinator:
switch (token.content) {
2023-03-14 09:13:23 +00:00
case PCombinator.Descendent:
isPureCSS = false;
if (storage.length) {
compoundSelector.push(storage.toStringAndClear());
}
compoundSelector = [];
complexSelector.push(PCombinator.Descendent);
complexSelector.push(compoundSelector);
continue;
2023-03-14 09:13:23 +00:00
case PCombinator.Child:
isPureCSS = false;
if (storage.length) {
compoundSelector.push(storage.toStringAndClear());
}
compoundSelector = [];
complexSelector.push(PCombinator.Child);
complexSelector.push(compoundSelector);
continue;
2023-02-15 18:42:32 +00:00
}
break;
2023-03-14 09:13:23 +00:00
case TokenType.PseudoElement:
if (!token.name.startsWith('-p-')) {
2023-02-15 18:42:32 +00:00
break;
}
isPureCSS = false;
if (storage.length) {
compoundSelector.push(storage.toStringAndClear());
2023-02-15 18:42:32 +00:00
}
compoundSelector.push({
name: token.name.slice(3),
value: unquote(token.argument ?? ''),
});
continue;
2023-03-14 09:13:23 +00:00
case TokenType.Comma:
if (storage.length) {
compoundSelector.push(storage.toStringAndClear());
2023-02-15 18:42:32 +00:00
}
compoundSelector = [];
complexSelector = [compoundSelector];
selectors.push(complexSelector);
continue;
2023-02-15 18:42:32 +00:00
}
storage.add(token);
2023-02-15 18:42:32 +00:00
}
if (storage.length) {
compoundSelector.push(storage.toStringAndClear());
}
return [selectors, isPureCSS];
2023-02-15 18:42:32 +00:00
}