123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- /*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */
- /*
- * This is the JSONSelect reference implementation, in javascript.
- */
- (function(exports) {
- var // localize references
- toString = Object.prototype.toString;
- function jsonParse(str) {
- try {
- if(JSON && JSON.parse){
- return JSON.parse(str);
- }
- return (new Function("return " + str))();
- } catch(e) {
- te("ijs");
- }
- }
- // emitted error codes.
- var errorCodes = {
- "ijs": "invalid json string",
- "mpc": "multiple pseudo classes (:xxx) not allowed",
- "mepf": "malformed expression in pseudo-function",
- "nmi": "multiple ids not allowed",
- "se": "selector expected",
- "sra": "string required after '.'",
- "uc": "unrecognized char",
- "ujs": "unclosed json string",
- "upc": "unrecognized pseudo class"
- };
- // throw an error message
- function te(ec) {
- throw new Error(errorCodes[ec]);
- }
- // THE LEXER
- var toks = {
- psc: 1, // pseudo class
- psf: 2, // pseudo class function
- typ: 3, // type
- str: 4 // string
- };
- var pat = /^(?:([\r\n\t\ ]+)|([*.,>])|(string|boolean|null|array|object|number)|(:(?:root|first-child|last-child|only-child))|(:(?:nth-child|nth-last-child))|(:\w+)|(\"(?:[^\\]|\\[^\"])*\")|(\")|((?:[_a-zA-Z]|[^\0-\0177]|\\[^\r\n\f0-9a-fA-F])(?:[_a-zA-Z0-9\-]|[^\u0000-\u0177]|(?:\\[^\r\n\f0-9a-fA-F]))*))/;
- var exprPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
- var lex = function (str, off) {
- if (!off) off = 0;
- var m = pat.exec(str.substr(off));
- if (!m) return undefined;
- off+=m[0].length;
- var a;
- if (m[1]) a = [off, " "];
- else if (m[2]) a = [off, m[0]];
- else if (m[3]) a = [off, toks.typ, m[0]];
- else if (m[4]) a = [off, toks.psc, m[0]];
- else if (m[5]) a = [off, toks.psf, m[0]];
- else if (m[6]) te("upc");
- else if (m[7]) a = [off, toks.str, jsonParse(m[0])];
- else if (m[8]) te("ujs");
- else if (m[9]) a = [off, toks.str, m[0].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")];
- return a;
- };
- // THE PARSER
- var parse = function (str) {
- var a = [], off = 0, am;
- while (true) {
- var s = parse_selector(str, off);
- a.push(s[1]);
- s = lex(str, off = s[0]);
- if (s && s[1] === " ") s = lex(str, off = s[0]);
- if (!s) break;
- // now we've parsed a selector, and have something else...
- if (s[1] === ">") {
- a.push(">");
- off = s[0];
- } else if (s[1] === ",") {
- if (am === undefined) am = [ ",", a ];
- else am.push(a);
- a = [];
- off = s[0];
- }
- }
- if (am) am.push(a);
- return am ? am : a;
- };
- var parse_selector = function(str, off) {
- var soff = off;
- var s = { };
- var l = lex(str, off);
- // skip space
- if (l && l[1] === " ") { soff = off = l[0]; l = lex(str, off); }
- if (l && l[1] === toks.typ) {
- s.type = l[2];
- l = lex(str, (off = l[0]));
- } else if (l && l[1] === "*") {
- // don't bother representing the universal sel, '*' in the
- // parse tree, cause it's the default
- l = lex(str, (off = l[0]));
- }
- // now support either an id or a pc
- while (true) {
- if (l === undefined) {
- break;
- } else if (l[1] === ".") {
- l = lex(str, (off = l[0]));
- if (!l || l[1] !== toks.str) te("sra");
- if (s.id) te("nmi");
- s.id = l[2];
- } else if (l[1] === toks.psc) {
- if (s.pc || s.pf) te("mpc");
- // collapse first-child and last-child into nth-child expressions
- if (l[2] === ":first-child") {
- s.pf = ":nth-child";
- s.a = 0;
- s.b = 1;
- } else if (l[2] === ":last-child") {
- s.pf = ":nth-last-child";
- s.a = 0;
- s.b = 1;
- } else {
- s.pc = l[2];
- }
- } else if (l[1] === toks.psf) {
- if (s.pc || s.pf ) te("mpc");
- s.pf = l[2];
- var m = exprPat.exec(str.substr(l[0]));
- if (!m) te("mepf");
- if (m[5]) {
- s.a = 2;
- s.b = (m[5] === "odd") ? 1 : 0;
- } else if (m[6]) {
- s.a = 0;
- s.b = parseInt(m[6], 10);
- } else {
- s.a = parseInt((m[1] ? m[1] : "+") + (m[2] ? m[2] : "1"),10);
- s.b = m[3] ? parseInt(m[3] + m[4],10) : 0;
- }
- l[0] += m[0].length;
- } else {
- break;
- }
- l = lex(str, (off = l[0]));
- }
- // now if we didn't actually parse anything it's an error
- if (soff === off) te("se");
- return [off, s];
- };
- // THE EVALUATOR
- function isArray(o) {
- return Array.isArray ? Array.isArray(o) :
- toString.call(o) === "[object Array]";
- }
- function mytypeof(o) {
- if (o === null) return "null";
- var to = typeof o;
- if (to === "object" && isArray(o)) to = "array";
- return to;
- }
- function mn(node, sel, id, num, tot) {
- var sels = [];
- var cs = (sel[0] === ">") ? sel[1] : sel[0];
- var m = true, mod;
- if (cs.type) m = m && (cs.type === mytypeof(node));
- if (cs.id) m = m && (cs.id === id);
- if (m && cs.pf) {
- if (cs.pf === ":nth-last-child") num = tot - num;
- else num++;
- if (cs.a === 0) {
- m = cs.b === num;
- } else {
- mod = ((num - cs.b) % cs.a);
- m = (!mod && ((num*cs.a + cs.b) >= 0));
- }
- }
- // should we repeat this selector for descendants?
- if (sel[0] !== ">" && sel[0].pc !== ":root") sels.push(sel);
- if (m) {
- // is there a fragment that we should pass down?
- if (sel[0] === ">") { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
- else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
- }
- return [m, sels];
- }
- function forEach(sel, obj, fun, id, num, tot) {
- var a = (sel[0] === ",") ? sel.slice(1) : [sel],
- a0 = [],
- call = false,
- i = 0, j = 0, l = 0, k, x;
- for (i = 0; i < a.length; i++) {
- x = mn(obj, a[i], id, num, tot);
- if (x[0]) {
- call = true;
- }
- for (j = 0; j < x[1].length; j++) {
- a0.push(x[1][j]);
- }
- }
- if (a0.length && typeof obj === "object") {
- if (a0.length >= 1) {
- a0.unshift(",");
- }
- if (isArray(obj)) {
- for (i = 0; i < obj.length; i++) {
- forEach(a0, obj[i], fun, undefined, i, obj.length);
- }
- } else {
- // it's a shame to do this for :last-child and other
- // properties which count from the end when we don't
- // even know if they're present. Also, the stream
- // parser is going to be pissed.
- l = 0;
- for (k in obj) {
- if (obj.hasOwnProperty(k)) {
- l++;
- }
- }
- i = 0;
- for (k in obj) {
- if (obj.hasOwnProperty(k)) {
- forEach(a0, obj[k], fun, k, i++, l);
- }
- }
- }
- }
- if (call && fun) {
- fun(obj);
- }
- }
- function match(sel, obj) {
- var a = [];
- forEach(sel, obj, function(x) {
- a.push(x);
- });
- return a;
- }
- function compile(sel) {
- return {
- sel: parse(sel),
- match: function(obj){
- return match(this.sel, obj);
- },
- forEach: function(obj, fun) {
- return forEach(this.sel, obj, fun);
- }
- };
- }
- exports._lex = lex;
- exports._parse = parse;
- exports.match = function (sel, obj) {
- return compile(sel).match(obj);
- };
- exports.forEach = function(sel, obj, fun) {
- return compile(sel).forEach(obj, fun);
- };
- exports.compile = compile;
- })(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
|