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.
242 lines
4.3 KiB
242 lines
4.3 KiB
9 years ago
|
/*!
|
||
|
* 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 };
|
||
|
};
|