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();
}
;