query-selector-ponyfill
Version:
use querySelector syntax to search for nodes inside of (nested) shadow roots
127 lines (125 loc) • 5.08 kB
JavaScript
;
/* istanbul ignore file */
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeSelector = void 0;
// normalize-selector-rev-02.js
/*
author: kyle simpson (@getify)
original source: https://gist.github.com/getify/9679380
modified for tests by david kaye (@dfkaye)
21 march 2014
rev-02 incorporate kyle's changes 3/2/42014
*/
function normalizeSelector(sel) {
// save unmatched text, if any
function saveUnmatched() {
if (unmatched) {
// whitespace needed after combinator?
if (tokens.length > 0 && /^[~+>]$/.test(tokens[tokens.length - 1])) {
tokens.push(' ');
}
// save unmatched text
tokens.push(unmatched);
}
}
var tokens = [], match, unmatched, regex, state = [0], next_match_idx = 0, prev_match_idx, not_escaped_pattern = /(?:[^\\]|(?:^|[^\\])(?:\\\\)+)$/, whitespace_pattern = /^\s+$/, state_patterns = [
/\s+|\/\*|["'>~+[(]/g,
/\s+|\/\*|["'[\]()]/g,
/\s+|\/\*|["'[\]()]/g,
null,
/\*\//g, // comment
];
sel = sel.trim();
// eslint-disable-next-line no-constant-condition
while (true) {
unmatched = '';
regex = state_patterns[state[state.length - 1]];
regex.lastIndex = next_match_idx;
match = regex.exec(sel);
// matched text to process?
if (match) {
prev_match_idx = next_match_idx;
next_match_idx = regex.lastIndex;
// collect the previous string chunk not matched before this token
if (prev_match_idx < next_match_idx - match[0].length) {
unmatched = sel.substring(prev_match_idx, next_match_idx - match[0].length);
}
// general, [ ] pair, ( ) pair?
if (state[state.length - 1] < 3) {
saveUnmatched();
// starting a [ ] pair?
if (match[0] === '[') {
state.push(1);
}
// starting a ( ) pair?
else if (match[0] === '(') {
state.push(2);
}
// starting a string literal?
else if (/^["']$/.test(match[0])) {
state.push(3);
state_patterns[3] = new RegExp(match[0], 'g');
}
// starting a comment?
else if (match[0] === '/*') {
state.push(4);
}
// ending a [ ] or ( ) pair?
else if (/^[\])]$/.test(match[0]) && state.length > 0) {
state.pop();
}
// handling whitespace or a combinator?
else if (/^(?:\s+|[~+>])$/.test(match[0])) {
// need to insert whitespace before?
if (tokens.length > 0 && !whitespace_pattern.test(tokens[tokens.length - 1]) && state[state.length - 1] === 0) {
// add normalized whitespace
tokens.push(' ');
}
// case-insensitive attribute selector CSS L4
if (state[state.length - 1] === 1 && tokens.length === 5 && tokens[2].charAt(tokens[2].length - 1) === '=') {
tokens[4] = ' ' + tokens[4];
}
// whitespace token we can skip?
if (whitespace_pattern.test(match[0])) {
continue;
}
}
// save matched text
tokens.push(match[0]);
}
// otherwise, string literal or comment
else {
// save unmatched text
tokens[tokens.length - 1] += unmatched;
// unescaped terminator to string literal or comment?
if (not_escaped_pattern.test(tokens[tokens.length - 1])) {
// comment terminator?
if (state[state.length - 1] === 4) {
// ok to drop comment?
if (tokens.length < 2 || whitespace_pattern.test(tokens[tokens.length - 2])) {
tokens.pop();
}
// otherwise, turn comment into whitespace
else {
tokens[tokens.length - 1] = ' ';
}
// handled already
match[0] = '';
}
state.pop();
}
// append matched text to existing token
tokens[tokens.length - 1] += match[0];
}
}
// otherwise, end of processing (no more matches)
else {
unmatched = sel.substr(next_match_idx);
saveUnmatched();
break;
}
}
return tokens.join('').trim();
}
exports.normalizeSelector = normalizeSelector;
//# sourceMappingURL=normalize.js.map