Skip to content

Instantly share code, notes, and snippets.

@dhruvbird
Created May 23, 2011 18:11
Show Gist options
  • Save dhruvbird/987196 to your computer and use it in GitHub Desktop.
Save dhruvbird/987196 to your computer and use it in GitHub Desktop.
JsonSelect
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);
/*! 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