skylark-utils
Version:
An Elegant HTML5 JavaScript Library.
860 lines (724 loc) • 25.3 kB
JavaScript
define([
"./skylark",
"./langx",
"./browser",
"./noder"
], function(skylark, langx, browser, noder, velm) {
var local = {},
filter = Array.prototype.filter,
slice = Array.prototype.slice,
nativeMatchesSelector = browser.matchesSelector;
/*
---
name: Slick.Parser
description: Standalone CSS3 Selector parser
provides: Slick.Parser
...
*/
;
(function() {
var parsed,
separatorIndex,
combinatorIndex,
reversed,
cache = {},
reverseCache = {},
reUnescape = /\\/g;
var parse = function(expression, isReversed) {
if (expression == null) return null;
if (expression.Slick === true) return expression;
expression = ('' + expression).replace(/^\s+|\s+$/g, '');
reversed = !!isReversed;
var currentCache = (reversed) ? reverseCache : cache;
if (currentCache[expression]) return currentCache[expression];
parsed = {
Slick: true,
expressions: [],
raw: expression,
reverse: function() {
return parse(this.raw, true);
}
};
separatorIndex = -1;
while (expression != (expression = expression.replace(regexp, parser)));
parsed.length = parsed.expressions.length;
return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
};
var reverseCombinator = function(combinator) {
if (combinator === '!') return ' ';
else if (combinator === ' ') return '!';
else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
else return '!' + combinator;
};
var reverse = function(expression) {
var expressions = expression.expressions;
for (var i = 0; i < expressions.length; i++) {
var exp = expressions[i];
var last = {
parts: [],
tag: '*',
combinator: reverseCombinator(exp[0].combinator)
};
for (var j = 0; j < exp.length; j++) {
var cexp = exp[j];
if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
cexp.combinator = cexp.reverseCombinator;
delete cexp.reverseCombinator;
}
exp.reverse().push(last);
}
return expression;
};
var escapeRegExp = (function() {
// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
var from = /(?=[\-\[\]{}()*+?.\\\^$|,#\s])/g,
to = '\\';
return function(string) {
return string.replace(from, to)
}
}())
var regexp = new RegExp(
"^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
.replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']')
.replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
.replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
);
function parser(
rawMatch,
separator,
combinator,
combinatorChildren,
tagName,
id,
className,
attributeKey,
attributeOperator,
attributeQuote,
attributeValue,
pseudoMarker,
pseudoClass,
pseudoQuote,
pseudoClassQuotedValue,
pseudoClassValue
) {
if (separator || separatorIndex === -1) {
parsed.expressions[++separatorIndex] = [];
combinatorIndex = -1;
if (separator) return '';
}
if (combinator || combinatorChildren || combinatorIndex === -1) {
combinator = combinator || ' ';
var currentSeparator = parsed.expressions[separatorIndex];
if (reversed && currentSeparator[combinatorIndex])
currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
currentSeparator[++combinatorIndex] = {
combinator: combinator,
tag: '*'
};
}
var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
if (tagName) {
currentParsed.tag = tagName.replace(reUnescape, '');
} else if (id) {
currentParsed.id = id.replace(reUnescape, '');
} else if (className) {
className = className.replace(reUnescape, '');
if (!currentParsed.classList) currentParsed.classList = [];
if (!currentParsed.classes) currentParsed.classes = [];
currentParsed.classList.push(className);
currentParsed.classes.push({
value: className,
regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
});
} else if (pseudoClass) {
pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
if (!currentParsed.pseudos) currentParsed.pseudos = [];
currentParsed.pseudos.push({
key: pseudoClass.replace(reUnescape, ''),
value: pseudoClassValue,
type: pseudoMarker.length == 1 ? 'class' : 'element'
});
} else if (attributeKey) {
attributeKey = attributeKey.replace(reUnescape, '');
attributeValue = (attributeValue || '').replace(reUnescape, '');
var test, regexp;
switch (attributeOperator) {
case '^=':
regexp = new RegExp('^' + escapeRegExp(attributeValue));
break;
case '$=':
regexp = new RegExp(escapeRegExp(attributeValue) + '$');
break;
case '~=':
regexp = new RegExp('(^|\\s)' + escapeRegExp(attributeValue) + '(\\s|$)');
break;
case '|=':
regexp = new RegExp('^' + escapeRegExp(attributeValue) + '(-|$)');
break;
case '=':
test = function(value) {
return attributeValue == value;
};
break;
case '*=':
test = function(value) {
return value && value.indexOf(attributeValue) > -1;
};
break;
case '!=':
test = function(value) {
return attributeValue != value;
};
break;
default:
test = function(value) {
return !!value;
};
}
if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function() {
return false;
};
if (!test) test = function(value) {
return value && regexp.test(value);
};
if (!currentParsed.attributes) currentParsed.attributes = [];
currentParsed.attributes.push({
key: attributeKey,
operator: attributeOperator,
value: attributeValue,
test: test
});
}
return '';
};
// Slick NS
var Slick = (this.Slick || {});
Slick.parse = function(expression) {
return parse(expression);
};
Slick.escapeRegExp = escapeRegExp;
if (!this.Slick) this.Slick = Slick;
}).apply(local);
var simpleClassSelectorRE = /^\.([\w-]*)$/,
simpleIdSelectorRE = /^#([\w-]*)$/,
slice = Array.prototype.slice;
local.parseSelector = local.Slick.parse;
local.pseudos = {
// custom pseudos
checked: function(elm) {
return !!elm.checked;
},
contains: function(elm, idx, nodes, text) {
if ($(this).text().indexOf(text) > -1) return this
},
'disabled': function(elm) {
return !!elm.disabled;
},
'enabled': function(elm) {
return !elm.disabled;
},
eq: function(elm, idx, nodes, value) {
return (idx == value);
},
'focus': function(elm) {
return document.activeElement === elm && (elm.href || elm.type || elm.tabindex);
},
first: function(elm, idx) {
return (idx === 0);
},
gt: function(elm, idx, nodes, value) {
return (idx > value);
},
has: function(elm, idx, nodes, sel) {
return local.querySelector(elm, sel).length > 0;
},
hidden: function(elm) {
return !local.pseudos["visible"](elm);
},
last: function(elm, idx, nodes) {
return (idx === nodes.length - 1);
},
lt: function(elm, idx, nodes, value) {
return (idx < value);
},
not: function(elm, idx, nodes, sel) {
return local.match(elm, sel);
},
parent: function(elm) {
return !!elm.parentNode;
},
selected: function(elm) {
return !!elm.selected;
},
visible: function(elm) {
return elm.offsetWidth && elm.offsetWidth
}
};
local.divide = function(cond) {
var nativeSelector = "",
customPseudos = [],
tag,
id,
classes,
attributes,
pseudos;
if (id = cond.id) {
nativeSelector += ("#" + id);
}
if (classes = cond.classes) {
for (var i = classes.length; i--;) {
nativeSelector += ("." + classes[i].value);
}
}
if (attributes = cond.attributes) {
for (var i = 0; i < attributes.length; i++) {
if (attributes[i].operator) {
nativeSelector += ("[" + attributes[i].key + attributes[i].operator + JSON.stringify(attributes[i].value) +"]");
} else {
nativeSelector += ("[" + attributes[i].key + "]");
}
}
}
if (pseudos = cond.pseudos) {
for (i = pseudos.length; i--;) {
part = pseudos[i];
if (this.pseudos[part.key]) {
customPseudos.push(part);
} else {
if (part.value !== undefined) {
nativeSelector += (":" + part.key + "(" + JSON.stringify(part))
}
}
}
}
if (tag = cond.tag) {
nativeSelector = tag.toUpperCase() + nativeSelector;
}
if (!nativeSelector) {
nativeSelector = "*";
}
return {
nativeSelector: nativeSelector,
customPseudos: customPseudos
}
};
local.check = function(node, cond, idx, nodes) {
var tag,
id,
classes,
attributes,
pseudos;
if (tag = cond.tag) {
var nodeName = node.nodeName.toUpperCase();
if (tag == '*') {
if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
} else {
if (nodeName != (tag || "").toUpperCase()) return false;
}
}
if (id = cond.id) {
if (node.getAttribute('id') != id) {
return false;
}
}
var i, part, cls, pseudo;
if (classes = cond.classes) {
for (i = classes.length; i--;) {
cls = node.getAttribute('class');
if (!(cls && classes[i].regexp.test(cls))) return false;
}
}
if (attributes)
for (i = attributes.length; i--;) {
part = attributes[i];
if (part.operator ? !part.test(node.getAttribute(part.key)) : !node.hasAttribute(part.key)) return false;
}
if (pseudos = cond.pseudos) {
for (i = pseudos.length; i--;) {
part = pseudos[i];
if (pseudo = this.pseudos[part.key]) {
if (!pseudo(node, idx, nodes, part.value)) {
return false;
}
} else {
if (!nativeMatchesSelector.call(node, part.key)) {
return false;
}
}
}
}
return true;
}
local.match = function(node, selector) {
var parsed = local.Slick.parse(selector);
if (!parsed) {
return true;
}
// simple (single) selectors
var expressions = parsed.expressions,
simpleExpCounter = 0,
i;
for (i = 0;
(currentExpression = expressions[i]); i++) {
if (currentExpression.length == 1) {
var exp = currentExpression[0];
if (this.check(node, exp)) {
return true;
}
simpleExpCounter++;
}
}
if (simpleExpCounter == parsed.length) {
return false;
}
var nodes = this.query(document, parsed),
item;
for (i = 0; item = nodes[i++];) {
if (item === node) {
return true;
}
}
return false;
};
local.combine = function(elm, bit) {
var op = bit.combinator,
cond = bit,
node1,
nodes = [];
switch (op) {
case '>': // direct children
nodes = children(elm, cond);
break;
case '+': // next sibling
node1 = nextSibling(elm, cond, true);
if (node1) {
nodes.push(node1);
}
break;
case '^': // first child
node1 = firstChild(elm, cond, true);
if (node1) {
nodes.push(node1);
}
break;
case '~': // next siblings
nodes = nextSiblings(elm, cond);
break;
case '++': // next sibling and previous sibling
var prev = previousSibling(elm, cond, true),
next = nextSibling(elm, cond, true);
if (prev) {
nodes.push(prev);
}
if (next) {
nodes.push(next);
}
break;
case '~~': // next siblings and previous siblings
nodes = siblings(elm, cond);
break;
case '!': // all parent nodes up to document
nodes = ancestors(elm, cond);
break;
case '!>': // direct parent (one level)
node1 = parent(elm, cond);
if (node1) {
nodes.push(node1);
}
break;
case '!+': // previous sibling
nodes = previousSibling(elm, cond, true);
break;
case '!^': // last child
node1 = lastChild(elm, cond, true);
if (node1) {
nodes.push(node1);
}
break;
case '!~': // previous siblings
nodes = previousSiblings(elm, cond);
break;
default:
var divided = this.divide(bit);
nodes = slice.call(elm.querySelectorAll(divided.nativeSelector));
if (divided.customPseudos) {
for (var i = divided.customPseudos.length - 1; i >= 0; i--) {
nodes = filter.call(nodes, function(item, idx) {
return local.check(item, {
pseudos: [divided.customPseudos[i]]
}, idx, nodes)
});
}
}
break;
}
return nodes;
}
local.query = function(node, selector, single) {
var parsed = this.Slick.parse(selector);
var
founds = [],
currentExpression, currentBit,
expressions = parsed.expressions;
for (var i = 0;
(currentExpression = expressions[i]); i++) {
var currentItems = [node],
found;
for (var j = 0;
(currentBit = currentExpression[j]); j++) {
found = langx.map(currentItems, function(item, i) {
return local.combine(item, currentBit)
});
if (found) {
currentItems = found;
}
}
if (found) {
founds = founds.concat(found);
}
}
return founds;
}
function ancestor(node, selector, root) {
var rootIsSelector = root && langx.isString(root);
while (node = node.parentNode) {
if (matches(node, selector)) {
return node;
}
if (root) {
if (rootIsSelector) {
if (matches(node,root)) {
break;
}
} else if (node == root) {
break;
}
}
}
return null;
}
function ancestors(node, selector,root) {
var ret = [],
rootIsSelector = root && langx.isString(root);
while (node = node.parentNode) {
if (matches(node, selector)) {
ret.push(node);
}
if (root) {
if (rootIsSelector) {
if (matches(node,root)) {
break;
}
} else if (node == root) {
break;
}
}
}
return ret;
}
function byId(id, doc) {
doc = doc || noder.doc();
return doc.getElementById(id);
}
function children(node, selector) {
var childNodes = node.childNodes,
ret = [];
for (var i = 0; i < childNodes.length; i++) {
var node = childNodes[i];
if (node.nodeType == 1) {
if (!selector || matches(node, selector)) {
ret.push(node);
}
}
}
return ret;
}
function closest(node, selector) {
while (node && !(matches(node, selector))) {
node = node.parentNode;
}
return node;
}
function descendants(elm, selector) {
// Selector
try {
return slice.call(elm.querySelectorAll(selector));
} catch (matchError) {
//console.log(matchError);
}
return local.query(elm, selector);
}
function descendant(elm, selector) {
// Selector
try {
return elm.querySelector(selector);
} catch (matchError) {
//console.log(matchError);
}
var nodes = local.query(elm, selector);
if (nodes.length > 0) {
return nodes[0];
} else {
return null;
}
}
function find(selector) {
return descendant(document.body, selector);
}
function findAll(selector) {
return descendants(document.body, selector);
}
function firstChild(elm, selector, first) {
var childNodes = elm.childNodes,
node = childNodes[0];
while (node) {
if (node.nodeType == 1) {
if (!selector || matches(node, selector)) {
return node;
}
if (first) {
break;
}
}
node = node.nextSibling;
}
return null;
}
function lastChild(elm, selector, last) {
var childNodes = elm.childNodes,
node = childNodes[childNodes.length - 1];
while (node) {
if (node.nodeType == 1) {
if (!selector || matches(node, selector)) {
return node;
}
if (last) {
break;
}
}
node = node.previousSibling;
}
return null;
}
function matches(elm, selector) {
if (!selector || !elm || elm.nodeType !== 1) {
return false
}
if (langx.isString(selector)) {
try {
return nativeMatchesSelector.call(elm, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
} catch (matchError) {
//console.log(matchError);
}
return local.match(elm, selector);
} else if (langx.isArrayLike(selector)) {
return langx.inArray(elm,selector);
} else if (langx.isPlainObject(selector)){
return local.check(elm, selector);
} else {
return elm === selector;
}
}
function nextSibling(elm, selector, adjacent) {
var node = elm.nextSibling;
while (node) {
if (node.nodeType == 1) {
if (!selector || matches(node, selector)) {
return node;
}
if (adjacent) {
break;
}
}
node = node.nextSibling;
}
return null;
}
function nextSiblings(elm, selector) {
var node = elm.nextSibling,
ret = [];
while (node) {
if (node.nodeType == 1) {
if (!selector || matches(node, selector)) {
ret.push(node);
}
}
node = node.nextSibling;
}
return ret;
}
function parent(elm, selector) {
var node = elm.parentNode;
if (node && (!selector || matches(node, selector))) {
return node;
}
return null;
}
function previousSibling(elm, selector, adjacent) {
var node = elm.previousSibling;
while (node) {
if (node.nodeType == 1) {
if (!selector || matches(node, selector)) {
return node;
}
if (adjacent) {
break;
}
}
node = node.previousSibling;
}
return null;
}
function previousSiblings(elm, selector) {
var node = elm.previousSibling,
ret = [];
while (node) {
if (node.nodeType == 1) {
if (!selector || matches(node, selector)) {
ret.push(node);
}
}
node = node.previousSibling;
}
return ret;
}
function siblings(elm, selector) {
var node = elm.parentNode.firstChild,
ret = [];
while (node) {
if (node.nodeType == 1 && node !== elm) {
if (!selector || matches(node, selector)) {
ret.push(node);
}
}
node = node.nextSibling;
}
return ret;
}
var finder = function() {
return finder;
};
langx.mixin(finder, {
ancestor: ancestor,
ancestors: ancestors,
byId: byId,
children: children,
closest: closest,
descendant: descendant,
descendants: descendants,
find: find,
findAll: findAll,
firstChild: firstChild,
lastChild: lastChild,
matches: matches,
nextSibling: nextSibling,
nextSiblings: nextSiblings,
parent: parent,
previousSibling: previousSibling,
previousSiblings: previousSiblings,
pseudos: local.pseudos,
siblings: siblings
});
return skylark.finder = finder;
});