/*! 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);