/* 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; });