258 lines
4.6 KiB
JavaScript
258 lines
4.6 KiB
JavaScript
/*!
|
|
* 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.initial()
|
|
|| 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.initial = function() {
|
|
if (!this.pos && '~' == this.str[0] && '/' == this.str[1]) {
|
|
this.nested = false;
|
|
this.skip(2);
|
|
return this.stack[0];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* '\' ('&' | '^')
|
|
*/
|
|
|
|
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]) {
|
|
this.nested = false;
|
|
|
|
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.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();
|
|
}, this).map(function(selector) {
|
|
return (selector.nested ? ' ' : '') + selector.val;
|
|
}).join('').trim();
|
|
}
|
|
} 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 };
|
|
};
|