Created
May 23, 2011 18:11
-
-
Save dhruvbird/987196 to your computer and use it in GitHub Desktop.
JsonSelect
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const js = require('../jsonselect.js'); | |
var obj = { | |
books: [ | |
{ name: "A Rough Ride", author: "Unknown" }, | |
{ name: "A Smooth Ride", author: "Known" }, | |
{ name: "Javascript", author: "Crockford" }, | |
{ name: "Node.js", author: "Dahl" }, | |
{ name: "C++", author: "Stroustrup" } | |
], | |
humans: [ | |
{ fname: "Dhruv", lname: "Matani", age: 27 }, | |
{ fname: "No", lname: "Name", age: 1 }, | |
{ fname: "Lady", lname: "Gaga", age: 25 } | |
] | |
}; | |
// console.log(obj); | |
// console.log(jsonselect); | |
var book_names = js(".books .name", obj).toArray(); | |
console.log(book_names); | |
var human_names = js(".humans .fname", obj).toArray(); | |
console.log(human_names); | |
var all_names = js('.books .name', obj).add('.humans .fname').add('.lname').toArray(); | |
console.log(all_names); | |
// Optionally add members from another object | |
var oses = { | |
os: [ | |
{ 'name': "Linux" }, | |
{ 'name': "Windoze" }, | |
{ 'name': "MacOSX" } | |
] | |
}; | |
var xtra = js('.books .name', obj).add('.os .name', oses).toArray(); | |
console.log(xtra); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */ | |
/* | |
* This is the JSONSelect reference implementation, in javascript. | |
*/ | |
;(function(exports) { | |
var jp = (typeof JSON !== 'undefined' ? JSON.parse : eval); | |
function jsonParse(s) { try { return jp(s); } catch(e) { te("ijs"); }; } | |
// emitted error codes. Strip this table for an, uh, "optimized build" | |
var _es = {}; // overshadow any globals when the table is stripped | |
/** --->>> **/ | |
var _es = { | |
"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 a full or abbreviated error message depending on the existence of the | |
// _es table | |
var te = function(ec) { throw (_es[ec] ? _es[ec] : "jsonselect error: "+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 am = undefined, a = [], off = 0; | |
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 Object.prototype.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; | |
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 { | |
m = (!((num - cs.b) % cs.a) && ((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]; | |
var a0 = []; | |
var call = false; | |
for (var i = 0; i < a.length; i++) { | |
var x = mn(obj, a[i], id, num, tot); | |
if (x[0]) call = true; | |
for (var 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 (var 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. | |
var l = 0; | |
for (var k in obj) if (obj.hasOwnProperty(k)) l++; | |
var i = 0; | |
for (var 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) } | |
}; | |
} | |
function JSONSelector(sel, obj, prev) { | |
this.sel = sel; | |
this.obj = obj; | |
var _m = [ ]; | |
if (sel && obj) { | |
// console.log("foo"); | |
_m = compile(sel).match(obj); | |
} | |
// console.log("_m:", _m); | |
var i = 0, c = 0; | |
if (prev) { | |
for (; i < prev.length; ++i) { | |
this[i] = prev[i]; | |
} | |
} | |
for (; c < _m.length; ++c, ++i) { | |
this[i] = _m[c]; | |
} | |
this.length = i; | |
} | |
JSONSelector.prototype = { | |
forEach: function(fun) { | |
compile(this.sel).forEach(this.obj, fun); | |
return this; | |
}, | |
toArray: function() { | |
var _a = [ ]; | |
var i; | |
for (i = 0; i < this.length; ++i) { | |
_a.push(this[i]); | |
} | |
return _a; | |
}, | |
add: function(sel, obj) { | |
if (!obj) { | |
obj = this.obj; | |
} | |
return new JSONSelector(sel, obj, this); | |
}, | |
map: function(fun) { | |
return new JSONSelector(null, null, this.toArray().map(fun)); | |
}, | |
filter: function(fun) { | |
return new JSONSelector(null, null, this.toArray().filter(fun)); | |
}, | |
slice: function(start, end) { | |
return new JSONSelector(null, null, this.toArray().slice(start, end)); | |
} | |
}; | |
module.exports = function(sel, obj) { | |
return new JSONSelector(sel, obj); | |
}; | |
// console.log("Exports:", exports); | |
exports = module.exports; | |
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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment