UNPKG

toloframework

Version:

Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.

249 lines (223 loc) 6.41 kB
/** * @module tree-walker * * @description * Add hooks on an object's tree. * * @example * var TW = require('tree-walker'); * var data = { * students: [ * { gender: "M", name: "John" }, * { gender: "F", name: "Arya" }, * { gender: "F", name: "Shae" }, * { gender: "M", name: "Tyron" }, * { gender: "M", name: "Jammy" } * ] * }; * * var a = new TW({ * "students/2": display * }); * a.walk( data ); * * var b = new TW({ * "students/[gender=M]": display * }); * b.walk( data ); */ // Matches this kind of strings: // * `[Var]` // * ` [Toto]` // * ` [Toto] ` // * ` [Toto ] ` // * ` [ Toto] ` // * `[Var=bob]` // * `[titi= Fla ga da Johnes ]` var rxTest = /^[ \t]*\[[ \t]*([a-z_][a-z_0-9]*)[ \t]*(=[^\]]+)?[ \t]*\]/i; /** * @example * var TreeWalker = require("tree-walker"); * var instance = new TreeWalker(opts); * @class TreeWalker */ var TreeWalker = function( actions ) { this._actions = compile.call( this, actions ); }; /** * @return void */ TreeWalker.prototype.walk = function( data ) { this._actions.forEach(function ( item ) { var action = item.action; var path = item.path; var tester = path[0]; tester( data, action, path, 1 ); }); }; function compile( actions ) { var out = []; var path, action; var compiledActions; var pathItems; for( path in actions ) { compiledActions = []; action = actions[path]; pathItems = explodePath( path ); out.push({ path: pathItems, action: action }); } return out; } /** * Transform `"a/*"` into `[["att", "a"], ["any"]]`. * Transform `"bob/** /[youp]"` into `[["att", "bob"], ["all"], ["tst", {youp: undefined}]]`. * Transform `"a/b"` into `[["att", "a"], ["att", "b"]]`. * Transform `"a/4"` into `[[att: "a"], [idx: 4]]`. * Transform `"[x=toto]"` into `[["tst", {x: 'toto'}]]`. * Transform `"[x=toto][y=foo]"` into `[["tst", {x: 'toto', y: 'foo'}]]`. */ function explodePath( path ) { var items = path.split( '/' ); return items.map(function( itm ) { itm = itm.trim(); if( itm == '*' ) return buildAny(); if( itm == '**' ) return buildAll(); if( itm.charAt(0) != '[' ) { // Not a test. var idx = parseInt( itm ); if( isNaN( idx ) ) { return buildAtt( itm ); } return buildIdx( idx ); } var tst = {}; while( itm.length > 0 ) { var m = itm.match(rxTest); if (!m) break; var key = m[1]; var val = m[2]; if (val) { val = val.substr(1); } tst[key] = val; // Go to next test, if any. itm = itm.substr( m[0].length ); } return buildTst( tst ); }); } function buildAtt( att ) { return function( node, action, path, idx ) { node = node[att]; if( typeof node === 'undefined' ) return false; if( idx < path.length ) { return path[idx]( node, action, path, idx + 1 ); } action( node ); return false; }; } function buildIdx( index ) { return function( node, action, path, idx ) { node = node[index]; if( typeof node === 'undefined' ) return false; if( idx < path.length ) { return path[idx]( node, action, path, idx + 1 ); } action( node ); return false; }; } function buildTst( tst ) { return function( node, action, path, idx ) { var arr = node; if( !Array.isArray( arr ) ) arr = [arr]; arr.forEach(function ( child ) { var success = true; var key, val; for( key in tst ) { val = tst[key]; if( typeof child[key] === 'undefined' ) { success = false; break; } if( typeof val !== 'undefined' ) { if( child[key] != val ) { success = false; break; } } } if( success ) { if( idx < path.length ) { return path[idx]( child, action, path, idx + 1 ); } action( child ); } }); }; } function actionAny( node, action, path, idx ) { if( typeof node === 'undefined' ) return; if( Array.isArray( node ) ) { node.forEach(function ( child ) { if( idx < path.length ) { path[idx]( child, action, path, idx + 1 ); } else { action( child ); } }); } else { var key, child; for( key in node ) { child = node[key]; if( idx < path.length ) { path[idx]( child, action, path, idx + 1 ); } else { action( child ); } } } } function buildAny() { return actionAny; } function actionAll( node, action, path, idx ) { if( typeof node === 'undefined' ) return; var test = path[idx]; var noMoreTests = (typeof test !== 'function'); if( isLeaf( node ) ) { if( noMoreTests ) action( node ); return; } if( Array.isArray( node ) ) { node.forEach(function ( child ) { if( !noMoreTests ) test( child, action, path, idx + 1 ); actionAll( child, action, path, idx ); }); } else { var key, child; for( key in node ) { child = node[key]; if( !noMoreTests ) test( child, action, path, idx + 1 ); actionAll( child, action, path, idx ); } } } function buildAll() { return actionAll; } function isLeaf( node ) { switch( typeof node ) { case 'function': case 'string': case 'number': case 'boolean': return true; } return false; } TreeWalker.create = function( actions ) { return new TreeWalker( actions ); }; module.exports = TreeWalker;