hexo/node_modules/stylus/lib/selector-parser.js

259 lines
4.8 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 };
};