notes/node_modules/normalize-selector/lib/normalize-selector.js

164 lines
4.1 KiB
JavaScript

/* normalize-selector v0.1.0 (c) 2014 Kyle Simpson */
(function UMD(name,context,definition){
if (typeof module !== "undefined" && module.exports) { module.exports = definition(); }
else if (typeof define === "function" && define.amd) { define(definition); }
else { context[name] = definition(name,context); }
})("normalizeSelector",this,function DEF(name,context){
"use strict";
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+$/,
attribute_nonspecial_pattern = /[^\s=~!^|$*\[\]\(\)]{2}/,
state_patterns = [
/\s+|\/\*|["'>~+\[\(]/g, // general
/\s+|\/\*|["'\[\]\(\)]/g, // [..] set
/\s+|\/\*|["'\[\]\(\)]/g, // (..) set
null, // string literal (placeholder)
/\*\//g // comment
]
;
sel = sel.trim();
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);
}
// need to force a space (possibly skipped
// previously by the parser)?
if (
state[state.length-1] === 1 &&
attribute_nonspecial_pattern.test(
tokens[tokens.length-1].substr(-1) +
unmatched.charAt(0)
)
) {
tokens.push(" ");
}
// 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(" ");
}
// 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();
}
return normalizeSelector;
});