You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
4.3 KiB
241 lines
4.3 KiB
/*! |
|
* Stylus - Selector Parser |
|
* Copyright (c) Automattic <developer.wordpress.com> |
|
* MIT Licensed |
|
*/ |
|
|
|
var COMBINATORS = ['>', '+', '~']; |
|
|
|
/** |
|
* Initialize a new `SelectorParser` |
|
* with the given `str` and selectors `stack`. |
|
* |
|
* @param {String} str |
|
* @param {Array} stack |
|
* @param {Array} parts |
|
* @api private |
|
*/ |
|
|
|
var SelectorParser = module.exports = function SelectorParser(str, stack, parts) { |
|
this.str = str; |
|
this.stack = stack || []; |
|
this.parts = parts || []; |
|
this.pos = 0; |
|
this.level = 2; |
|
this.nested = true; |
|
this.ignore = false; |
|
}; |
|
|
|
/** |
|
* Consume the given `len` and move current position. |
|
* |
|
* @param {Number} len |
|
* @api private |
|
*/ |
|
|
|
SelectorParser.prototype.skip = function(len) { |
|
this.str = this.str.substr(len); |
|
this.pos += len; |
|
}; |
|
|
|
/** |
|
* Consume spaces. |
|
*/ |
|
|
|
SelectorParser.prototype.skipSpaces = function() { |
|
while (' ' == this.str[0]) this.skip(1); |
|
}; |
|
|
|
/** |
|
* Fetch next token. |
|
* |
|
* @return {String} |
|
* @api private |
|
*/ |
|
|
|
SelectorParser.prototype.advance = function() { |
|
return this.root() |
|
|| this.relative() |
|
|| this.escaped() |
|
|| this.parent() |
|
|| this.partial() |
|
|| this.char(); |
|
}; |
|
|
|
/** |
|
* '/' |
|
*/ |
|
|
|
SelectorParser.prototype.root = function() { |
|
if (!this.pos && '/' == this.str[0] |
|
&& 'deep' != this.str.slice(1, 5)) { |
|
this.nested = false; |
|
this.skip(1); |
|
} |
|
}; |
|
|
|
/** |
|
* '../' |
|
*/ |
|
|
|
SelectorParser.prototype.relative = function(multi) { |
|
if ((!this.pos || multi) && '../' == this.str.slice(0, 3)) { |
|
this.nested = false; |
|
this.skip(3); |
|
while (this.relative(true)) this.level++; |
|
if (!this.raw) { |
|
var ret = this.stack[this.stack.length - this.level]; |
|
if (ret) { |
|
return ret; |
|
} else { |
|
this.ignore = true; |
|
} |
|
} |
|
} |
|
}; |
|
|
|
/** |
|
* '\' ('&' | '^') |
|
*/ |
|
|
|
SelectorParser.prototype.escaped = function() { |
|
if ('\\' == this.str[0]) { |
|
var char = this.str[1]; |
|
if ('&' == char || '^' == char) { |
|
this.skip(2); |
|
return char; |
|
} |
|
} |
|
}; |
|
|
|
/** |
|
* '&' |
|
*/ |
|
|
|
SelectorParser.prototype.parent = function() { |
|
if ('&' == this.str[0]) { |
|
if (!this.pos && (!this.stack.length || this.raw)) { |
|
var i = 0; |
|
while (' ' == this.str[++i]) ; |
|
if (~COMBINATORS.indexOf(this.str[i])) { |
|
this.skip(i + 1); |
|
return; |
|
} |
|
} |
|
|
|
this.nested = false; |
|
this.skip(1); |
|
if (!this.raw) |
|
return this.stack[this.stack.length - 1]; |
|
} |
|
}; |
|
|
|
/** |
|
* '^[' range ']' |
|
*/ |
|
|
|
SelectorParser.prototype.partial = function() { |
|
if ('^' == this.str[0] && '[' == this.str[1]) { |
|
this.skip(2); |
|
this.skipSpaces(); |
|
var ret = this.range(); |
|
this.skipSpaces(); |
|
if (']' != this.str[0]) return '^['; |
|
this.nested = false; |
|
this.skip(1); |
|
if (ret) { |
|
return ret; |
|
} else { |
|
this.ignore = true; |
|
} |
|
} |
|
}; |
|
|
|
/** |
|
* '-'? 0-9+ |
|
*/ |
|
|
|
SelectorParser.prototype.number = function() { |
|
var i = 0, ret = ''; |
|
if ('-' == this.str[i]) |
|
ret += this.str[i++]; |
|
|
|
while (this.str.charCodeAt(i) >= 48 |
|
&& this.str.charCodeAt(i) <= 57) |
|
ret += this.str[i++]; |
|
|
|
if (ret) { |
|
this.skip(i); |
|
return Number(ret); |
|
} |
|
}; |
|
|
|
/** |
|
* number ('..' number)? |
|
*/ |
|
|
|
SelectorParser.prototype.range = function() { |
|
var start = this.number() |
|
, ret; |
|
if ('..' == this.str.slice(0, 2)) { |
|
this.skip(2); |
|
var end = this.number() |
|
, len = this.parts.length; |
|
|
|
if (start < 0) start = len + start - 1; |
|
if (end < 0) end = len + end - 1; |
|
|
|
if (start > end) { |
|
var tmp = start; |
|
start = end; |
|
end = tmp; |
|
} |
|
|
|
if (end < len - 1) { |
|
ret = this.parts.slice(start, end + 1).map(function(part) { |
|
var selector = new SelectorParser(part, this.stack, this.parts); |
|
selector.raw = true; |
|
return selector.parse().val.trim(); |
|
}, this).join(' '); |
|
} |
|
} else { |
|
ret = this.stack[ |
|
start < 0 ? this.stack.length + start - 1 : start |
|
]; |
|
} |
|
|
|
if (ret) { |
|
return ret; |
|
} else { |
|
this.ignore = true; |
|
} |
|
}; |
|
|
|
/** |
|
* .+ |
|
*/ |
|
|
|
SelectorParser.prototype.char = function() { |
|
var char = this.str[0]; |
|
this.skip(1); |
|
return char; |
|
}; |
|
|
|
/** |
|
* Parses the selector. |
|
* |
|
* @return {Object} |
|
* @api private |
|
*/ |
|
|
|
SelectorParser.prototype.parse = function() { |
|
var val = ''; |
|
while (this.str.length) { |
|
val += this.advance() || ''; |
|
if (this.ignore) { |
|
val = ''; |
|
break; |
|
} |
|
} |
|
return { val: val.trimRight(), nested: this.nested }; |
|
};
|
|
|