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