silk.html.selector = xb.core.object.extend( { } ); silk.html.selector.library = xb.core.object.extend( { ctor: function() { this.__list = []; this.__index = {}; }, add: function( stringList, nameList, action ) { var stringList = ( stringList instanceof Array ) ? stringList : [ stringList ]; for ( var i = 0, il = stringList.length; i < il; i++ ) { var rule = silk.html.selector.rule( stringList[ i ], action ); var nameList = ( nameList instanceof Array ) ? nameList : [ nameList ]; for ( var j = 0, jl = nameList.length; j < jl; j++ ) { this.addRule( rule, nameList[ j ] ); } } return this; }, addRule: function( rule, name ) { if ( typeof( this.__index[ name ] ) === "undefined" ) { this.__index[ name ] = silk.html.selector.rule.set(); } this.__index[ name ].add( rule ); this.__list = []; return this; }, get: function( name ) { var set = this.__index[ name ]; if ( typeof( set ) === "undefined" ) { return null; } return set; }, link: function() { var ctx = { stack: [], linked: [] }; for ( var i in this.__index ) { this.__index[ i ].link( this, ctx ); } this.__list = ctx.linked; return this; }, query: function( stringList ) { var stringList = ( stringList instanceof Array ) ? stringList : [ stringList ]; if ( this.__list.length === 0 ) { this.link(); } var set = silk.html.selector.rule.set(); for ( var i = 0, il = stringList.length; i < il; i++ ) { set.add( silk.html.selector.rule( stringList[ i ] ) ); } set.link( this, { stack: [], linked: this.__list } ) return set; }, } ); silk.html.selector.rule = xb.core.object.extend( { ctor: function( string, action ) { this.__list = []; this.__elements = silk.html.selector.parse( string ); this.__action = ( typeof( action ) === "function" ) ? action : null; }, link: function( library, ctx ) { this.__list = []; for ( var i = 0, il = this.__elements.length; i < il; i++ ) { var elm = this.__elements[ i ]; var css = elm.getCSS(); var item = { elm: elm, modules: {}, string: css.string, specificity: css.specificity, full: { string: "", specificity: [] } }; for ( var m = 0, ml = elm.__modules.length; m < ml; m++ ) { var moduleName = elm.__modules[ m ]; //console.warn( "going to link ruleset", setName ); var module = library.get( moduleName ); if ( module === null ) { console.error( "No module with name", moduleName ); continue; } var linked = module.link( library, ctx ); if ( linked === null ) { console.error( "No linked module @", moduleName ); continue; } var s = linked.specificity(); for ( var j = 0, jl = item.specificity.length; j < jl; j++ ) { item.specificity[ j ] = item.specificity[ j ] + s[ j ]; } item.modules[ moduleName ] = linked; } if ( i === 0 ) { item.full.string = item.string; item.full.specificity = item.specificity; } else { item.full.string = this.__list[ i - 1 ].full.string + " " + elm.__combinator + " " + item.string; for ( var j = 0, jl = item.specificity.length; j < jl; j++ ) { item.full.specificity[ j ] = item.specificity[ j ] + this.__list[ i - 1 ].full.specificity[ j ]; } } this.__list.push( item ); } return this; }, string: function( domNode ) { return this.__list[ this.__list.length - 1 ].full.string; }, match: function( domNode ) { var result = null; if ( this.__list.length > 0 ) { result = this.matchElm( domNode, this.__list.length - 1 ); if ( result !== null ) { result.length = this.__list.length; if ( this.__action !== null ) { return this.__action.call( null, result ); } } } return result; }, handleEvent: function( evt, domNode ) { var result = null; if ( this.__list.length > 0 ) { result = this.matchElm( domNode, this.__list.length - 1 ); if ( result !== null ) { result.length = this.__list.length; if ( this.__action !== null ) { return this.__action.call( null, result, evt ); } } } return result; }, matchElm: function( domNode, i ) { var item = this.__list[ i ]; if ( !domNode.matches( item.full.string ) ) { // if ( i === this.__list.length - 1 ) { // console.warn( "full miss", item.full.string, domNode ); // } return null; } var resultNode = { domNode: domNode }; for ( var name in item.modules ) { resultNode[ name ] = item.modules[ name ].match( domNode, false ); if ( resultNode[ name ] === null ) { return null; } for ( var n in resultNode[ name ] ) { resultNode[ n ] = resultNode[ name ][ n ]; } } var result = {}; result[ this.__list.length - 1 - i ] = resultNode; if ( item.elm.__var !== null ) { result[ item.elm.__var ] = resultNode; } if ( i > 0 ) { var p = domNode; do { var n = true; switch ( item.elm.__combinator ) { default: console.error( "No valid combinator!", item.elm.__combinator ); case ">": n = false; case "": case ">>": p = p.parentElement; break; case "+": n = false; case "~": p = p.previousElementSibling; break; } if ( p === null ) { return null; } var m = this.matchElm( p, i - 1 ); if ( m !== null ) { for ( var e in m ) { result[ e ] = m[ e ]; } return result; } } while( n ); return null; } return result; }, specify: function( rule ) { var o = rule.specificity(); var t = this.specificity(); for ( var i = 0, il = t.length; i < il; i++ ) { var c = t[ i ] - o[ i ]; if ( c !== 0 ) { return c; } } return c; }, specificity: function() { return this.__list[ this.__list.length - 1 ].full.specificity; }, } ); silk.html.selector.rule.set = xb.core.object.extend( { ctor: function() { this.__list = []; }, add: function( rule ) { this.__list.push( rule ); return this; }, find: function( domNode ) { var domNode = ( typeof( domNode ) !== "object" ) ? document.body : domNode; var s = Date.now(); var l = []; for ( var i = 0, il = this.__list.length; i < il; i++ ) { l.push( this.__list[ i ].string() ); } var result = []; var q = l.join( ", " ); var nodes = domNode.querySelectorAll( q ); console.log( "nodes", q, nodes.length ); for ( var i = 0, il = nodes.length; i < il; i++ ) { var m = this.match( nodes[ i ], false ); if ( m !== null ) { result.push( m ); } } console.warn( "TOTAL time", ( Date.now() - s ) / 1000 ); return result; }, match: function( domNode, traversal ) { var fn = function( domNode, traversal ) { if ( domNode === null ) { return null; } var traversal = ( typeof( traversal ) === "undefined" ) ? ">>" : traversal; for ( var i = this.__list.length - 1; i >= 0; i-- ) { var m = this.__list[ i ].match( domNode ); if ( m !== null ) { return m; } } switch ( traversal ) { case "~": return fn.call( this, domNode.previousElementSibling, traversal ); case ">>": case "": return fn.call( this, domNode.parentElement, traversal ); } return null; } ; return fn.call( this, domNode, traversal ); }, handleEvent: function( evt ) { var fn = function( domNode ) { if ( domNode === null ) { return null; } for ( var i = this.__list.length - 1; i >= 0; i-- ) { var m = this.__list[ i ].handleEvent( evt, domNode ); if ( evt.__stopImmediatePropagationValue === true ) { return null; } } if ( evt.__stopPropagationValue === true ) { return null; } return fn.call( this, domNode.parentElement ); } ; return fn.call( this, evt.target ); }, __addRuleSorted: function( list, rule ) { var i = 0, il = list.length; for ( ; i < il; i++ ) { var s = rule.specify( list[ i ] ); if ( s < 0 ) { list.splice( i, 0, rule ); return this; } } list.push( rule ); return this; }, link: function( library, ctx ) { if ( ctx.linked.indexOf( this ) >= 0 ) { return this; } if ( ctx.stack.indexOf( this ) >= 0 ) { console.error( "silk.html.selector.rule.set::link", "recursion detected while linking", ctx.stack ); return null; } ctx.stack.push( this ); var list = []; for ( var i = 0, il = this.__list.length; i < il; i++ ) { this.__addRuleSorted( list, this.__list[ i ].link( library, ctx ) ); } ctx.stack.pop(); this.__list = list; ctx.linked.push( this ); return this; }, specificity: function() { return this.__list[ this.__list.length - 1 ].specificity(); }, } ); silk.html.selector.elm = xb.core.object.extend( { ctor: function() { this.__combinator = null; this.__var = null; this.__modules = []; this.__tagName = ""; this.__id = ""; this.__classes = []; this.__attributes = []; this.__pseudoClasses = []; this.__pseudoElements = []; }, getCSS: function() { var string = ""; var specificity = [ this.__modules.length, 0, 0, 0 ]; if ( this.__tagName.length && this.__tagName !== "*" ) { specificity[ 3 ] += 1; string += this.__tagName; } else { string += "*"; } if ( this.__id.length ) { specificity[ 1 ] += 1; string += "#" + this.__id; } for ( var i = 0, il = this.__classes.length; i < il; i++ ) { var c = this.__classes[ i ]; if ( c.length ) { specificity[ 2 ] += 1; string += "." + c; } } for ( var i = 0, il = this.__attributes.length; i < il; i++ ) { var a = this.__attributes[ i ]; specificity[ 2 ] += 1; var s = ""; s += a.name; if ( a.op ) { s += " " + a.op + " '" + a.value.replace( "'", "\\'" ) + "'"; } string += "[" + s + "]"; } for ( var i = 0, il = this.__pseudoClasses.length; i < il; i++ ) { var c = this.__pseudoClasses[ i ]; specificity[ 2 ] += 1; string += ":" + c.name; if ( c.arguments ) { string += "(" + c.arguments + ")"; } } return { specificity: specificity, string: string }; }, } ); silk.html.selector.token = function( str, state ) { var state = ( typeof( state ) !== "undefined" ) ? state : 0 ; if ( str.length >= 2 ) { var v = str.substr( 0, 2 ); if ( state === 0 ) { switch ( v ) { case "::": return { token: "PSEUDO_ELEMENT", value: v, next: str.substr( v.length ) }; break; case ":=": return { token: "ASSIGN", value: v, next: str.substr( 2 ) }; break; case "$(": return { token: "MODULE", value: v, next: str.substr( 2 ) }; break; case ">>": return { token: "COMBINATOR", value: v, next: str.substr( v.length ) }; break; case "~=": case "|=": case "^=": case "$=": case "*=": return { token: "ATTRIB_OP", value: v, next: str.substr( v.length ) }; break; } if ( v[ 0 ] === "\\" ) { return { token: "ANY", value: v, next: str.substr( v.length ) }; } } } if ( str.length >= 1 ) { var v = str.substr( 0, 1 ); if ( state === 0 ) { var t = null; switch( v ) { case ".": return { token: "CLASS", value: v, next: str.substr( v.length ) }; break; case "#": return { token: "ID", value: v, next: str.substr( v.length ) }; break; case ":": return { token: "PSEUDO_CLASS", value: v, next: str.substr( v.length ) }; break; case "+": case "~": return { token: "COMBINATOR", value: v, next: str.substr( v.length ) }; break; case ">": return { token: "COMBINATOR", value: v, next: str.substr( v.length ) }; break; case " ": case "\t": case "\r": case "\n": return { token: "WS", value: v, next: str.substr( v.length ) }; break; case ",": case "(": case ")": case "[": case "]": return { token: v, value: v, next: str.substr( v.length ) }; break; case "=": return { token: "ATTRIB_OP", value: v, next: str.substr( v.length ) }; break; case "'": case '"': var i = 1; var r = ""; while ( i < str.length && str[ i ] !== v ) { if ( str[ i ] === "\\" ) { i++; if ( i >= str.length ) { break; } } r += str[ i ]; i++; } return { token: "ANY", value: r, next: str.substr( i + 1 ) }; break; }; } return { token: "ANY", value: v, next: str.substr( v.length ) }; } return { token: null, value: "", next: "" }; } ; silk.html.selector.parse = function( string ) { var token = silk.html.selector.token , str = "" + string.trim() , t = token( str ) , p_ident = function( trim ) { var r = "" , trim = ( trim === true ) ? true : false ; if ( trim ) p_ws() ; while ( t.token === "ANY" ) { r += t.value; str = t.next; t = token( str ); } if ( trim ) p_ws(); ; return r; } , p_modules = function() { var r = [] , ident = null ; if ( t.token === "MODULE" ) { str = t.next; t = token( str ); do { ident = p_ident( true ); if ( ident.length ) { r.push( ident ); } } while ( ident.length ); if ( t.token === ")" ) { str = t.next; t = token( str ); } } return r; } , p_assign = function() { var r = null ; if ( t.token === "ASSIGN" ) { str = t.next; t = token( str ); r = p_ident( true ); } return r; } , p_ws = function() { var r = "" ; while ( t.token === "WS" ) { r += t.value; str = t.next; t = token( str ); } return r; } , p_attributes = function() { var name = "" , op = "" , value = "" ; if ( t.value === "[" ) { str = t.next; t = token( str ); p_ws(); name = p_ident(); p_ws(); if ( t.token === "ATTRIB_OP" ) { op = t.value; str = t.next; t = token( str ); p_ws(); value = p_ident(); str = t.next; t = token( str ); p_ws(); } if ( t.value === "]" ) { str = t.next; t = token( str ); } return { name: name, op: op, value: value }; } return null; } , p_pseudo_rest = function() { var r = "" ; if ( t.value === "(" ) { str = t.next; t = token( str ); do { if ( t.token === null || t.value === ")" ) { str = t.next; t = token( str ); return r; } if ( t.value === "(" ) { r += "(" + p_pseudo_rest() + ")"; continue; } r += t.value; str = t.next; t = token( str ); } while ( true ); } return ""; } , p_elm = function() { var result = silk.html.selector.elm() ; var l = str.length; result.__combinator = p_combinator(); result.__modules = p_modules(); result.__tagName = p_ident(); do { if ( t.token === "CLASS" ) { str = t.next; t = token( str ); var v = p_ident(); if ( result.__classes.indexOf( v ) < 0 ) { result.__classes.push( v ); } continue; } if ( t.token === "ID" ) { str = t.next; t = token( str ); result.__id = p_ident(); continue; } if ( t.value === "::" ) { str = t.next; t = token( str ); result.__pseudoElements.push( p_ident() ); continue; } if ( t.value === ":" ) { str = t.next; t = token( str ); result.__pseudoClasses.push( { name: p_ident(), arguments: p_pseudo_rest() } ); continue; } if ( t.value === "[" ) { result.__attributes.push( p_attributes() ); continue; } break; // exit loop } while( true ); p_ws(); result.__var = p_assign(); if ( l === str.length ) { return null; } return result; } , p_combinator = function() { var r = null , ws = p_ws() ; if ( t.token === "COMBINATOR" ) { r = t.value; str = t.next; t = token( str ); p_ws(); } if ( r === null || r === ">>" ) { r = ""; } return r; } , p_elmList = function() { var r = [] , elm = null ; do { elm = p_elm(); if ( elm !== null ) { r.push( elm ); } } while ( elm !== null ); return r; } ; return p_elmList(); } ;