2 * Original code by ofk ( kQuery, ksk )
\r
3 * http://d.hatena.ne.jp/ofk/comment/20090106/1231258010
\r
4 * http://d.hatena.ne.jp/ofk/20090111/1231668170
\r
7 * http://standards.mitsue.co.jp/resources/w3c/TR/css3-selectors/#nth-child-pseudo
\r
13 'nth-last-child' : 14,
\r
15 'nth-last-of-type' : 16,
\r
29 'last-of-type' : 12,
\r
30 'only-of-type' : 12,
\r
31 'first-of-type' : 13
\r
38 '+' : 3, // 兄弟セレクタ,共通の親を持つ、1番目要素が2番目要素の1つ前にある
\r
39 '~' : 4, // 一般兄弟セレクタ,共通の親を持つ、1番目要素が2番目要素の前 (直前でなくともよい) にある
\r
54 _OPERATORS : { '==' : 1, '!=': 2, '~=': 3, '^=': 4, '$=': 5, '*=': 6, '|=': 7 },
\r
55 _ALPHABET : 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-0123456789\\',
\r
56 _NUMBER : '+-0123456789'
\r
61 * 結合子 + 単体セレクタ( タグ,*,#,.,[],: )
\r
62 * ' ' 子孫セレクタ, '>' 直下,'+','~' の場合、tagName|* を返す
\r
63 * return [ pointer, [ selector, ... ] ] error: return pointer
\r
65 X.Dom.Query._parse = function( query, last ){
\r
66 var COMBINATOR = X.Dom.Query._COMBINATOR,
\r
67 SELECTOR = X.Dom.Query._SELECTOR,
\r
68 OPERATORS = X.Dom.Query._OPERATORS,
\r
69 ALPHABET = X.Dom.Query._ALPHABET,
\r
70 NUMBER = X.Dom.Query._NUMBER,
\r
77 chr, chrCode, nameChr, name1st,
\r
78 tmp, escape, quot, start,
\r
79 name, key, value, operator, a, b, not;
\r
82 chr = query.charAt( ++i );
\r
83 chrCode = ALPHABET.indexOf( chr );
\r
84 nameChr = chrCode !== -1;
\r
85 name1st = nameChr && chrCode < 52;
\r
88 name1st ? // tagName
\r
89 ( ( selector = 1 ) && ( phase = 0x2 ) && ( start = i ) ) :
\r
90 !not && ( tmp = COMBINATOR[ chr ] ) ? (
\r
91 ( 1 < tmp && 1 < combinator ) ?
\r
93 ( phase = tmp === 5 ? 0xe : 0x0 ) & ( ( 1 < tmp || combinator < 1 ) && ( combinator = tmp ) ) & ( tmp === 5 && ++i ) ) : // ' ' でない結合子の上書きはエラー
\r
94 ( tmp = SELECTOR[ chr ] ) ? // [
\r
95 ( selector = tmp ) && ( phase = selector === 5 ? 0x4 : 0x1 ) : // 7:[, 0<:
\r
97 ( ( selector = 1 ) && ( name = chr ) && ( phase = 0xe ) && ++i ) :
\r
98 chr !== ' ' && ( phase = 0xf );
\r
99 //console.log( '0x0: ' + name1st + ' ' + chrCode + ' ' + chr + ' ' + phase + ' tmp:' + tmp + ' comb:' + combinator );
\r
103 ( ( start = i ) && ( phase = 0x2 ) ) :
\r
104 chr !== ' ' && ( phase = 0xf );
\r
107 !nameChr && !( escape && ( selector === 2 || selector === 3 ) && ( chr === ':' || chr === '.' ) ) ? // id or class の場合 : . を直前にエスケープした場合に限り使える
\r
108 ( name = query.substring( start, i ) ) && ( phase = selector === 4 && name !== 'not' && chr === '(' ? 0x8 : 0xe ) :
\r
109 SELECTOR[ chr ] < 4 && ( phase = 0xe );
\r
115 case 0x4 : // start attr filter
\r
117 ( ( phase = 0x5 ) && ( start = i ) ) :
\r
120 case 0x5 : // attr filter key
\r
122 ( ( operator = 1 ) && ( phase = 0x6 ) && ( key || ( key = query.substring( start, i ) ) ) && ( start = i + 1 ) ) :
\r
124 ( !( operator = 0 ) && ( phase = 0xe ) && ( key || ( key = query.substring( start, i ) ) ) && ++i ) :
\r
126 ( key || ( key = query.substring( start, i ) ) ) :
\r
127 ( operator = OPERATORS[ query.substr( i, 2 ) ] ) ?
\r
128 ( ( phase = 0x6 ) && ( key || ( key = query.substring( start, i ) ) ) && ( start = ++i ) ) :
\r
129 !nameChr && ( phase = 0xf );
\r
130 //console.log( name1st + ' ' + chrCode + chr + phase );
\r
133 ( chr === '"' || chr === "'" ) && !escape && !quot ?
\r
134 ( quot = chr ) && ( start = i + 1 ) && ( phase = 0x7 ) :
\r
135 chr !== ' ' && ( start = i ) && ( phase = 0x7 );
\r
138 case 0x7 : // attr filter value
\r
140 !escape && !value && ( value = query.substring( start, i ) ) :
\r
142 ( ( value || ( value = query.substring( start, i ) ) ) && ( phase = 0xe ) && ++i ) :
\r
143 chr === ' ' && !quot && !value && ( value = query.substring( start, i ) );
\r
144 //( chr === '"' || chr === "'" ) && !quot && ( quot = chr ) && ( start = i + 1 );
\r
147 case 0x8 : // 4, 2n, even, odd, -n+4,
\r
148 NUMBER.indexOf( chr ) !== -1 ?
\r
149 ( start = i ) && ( phase = 0x9 ) :
\r
151 ( phase = 0xa ) && ( a = 1 ) && ( start = i + 1 ) :
\r
152 query.substr( i, 4 ) === 'even' ?
\r
153 ( ( a = 2 ) && !( b = 0 ) && ( i += 3 ) ) :
\r
154 query.substr( i, 3 ) === 'odd' ?
\r
155 ( ( a = 2 ) && ( b = 1 ) && ( i += 2 ) ) :
\r
156 chr === ')' && ( phase = a ? 0xe : 0xf ) && ++i;
\r
157 //console.log( '0x8: ' + name1st + ' ' + chrCode + ' ' + chr + ' ' + phase );
\r
160 tmp = query.substring( start, i );
\r
162 ( phase = 0xa ) && ( start = i + 1 ) && ( a = tmp === '+' ? 1 : tmp === '-' ? -1 : parseFloat( tmp ) ) :
\r
163 chr === ')' && ( phase = 0xe ) && ( b = parseFloat( tmp ) ) && ++i && ( a = 0 );
\r
164 //console.log( '0x9: ' + name1st + ' ' + chrCode + ' ' + chr + ' ' + phase );
\r
167 chr === ')' && ( phase = 0xe ) && ++i && ( b = parseFloat( query.substring( start, i ) ) || 0 );
\r
168 //console.log( '0xa: ' + start + ' ' + i );
\r
172 //alert( chr + ' ' + phase + ' ' + selector + ' ' + name + ' ' + name1st )
\r
173 if( phase === 0xe ){
\r
174 if( selector === 4 ){// :not
\r
175 if( name === 'not' ){
\r
176 if( not ) return i; // error
\r
183 if( a !== a || b !== b ) return i;
\r
184 result = [ not ? 0 : combinator, selector, name, a, b ];
\r
187 // lang result = [ not ? 0 : combinator, selector, name, lan ];
\r
188 // contains result = [ not ? 0 : combinator, selector, name, text ];
\r
194 [ not ? 0 : combinator, selector, key, operator, value ] :
\r
195 [ not ? 0 : combinator, selector, name.split( '\\' ).join( '' ) ];
\r
198 if( phase === 0xf ) return i;
\r
200 escape = chr === '\\' && !escape;
\r
202 //if( phase !== 0xe ) return i;
\r
203 if( not && ( tmp = query.substr( i ).indexOf( ')' ) ) === -1 ) return i;
\r
204 return not ? [ i + tmp + 1, [ combinator, 6, result ] ] : [ i, result ];
\r
208 X.Dom.find = X._shortcut = Node.prototype.find = X.Dom.NodeList.prototype.find = function( queryString ){
\r
209 var HTML = Node._html,
\r
210 scope = this.constructor === X.Dom.NodeList && this.length ? this : [ this.constructor === Node ? this : Node.root ],
\r
211 parents = scope, // 探索元の親要素 XNodeList の場合あり
\r
212 noLower = 'title id name class for ' + X.Dom.DTD.ATTR_VAL_IS_URI.join( ' ' ),
\r
213 ARY_PUSH = Array.prototype.push,
\r
215 root = X.Dom.Node.getRoot( scope[ 0 ] ),
\r
216 isXML = !!X.Dom.Node.isXmlDocument( root ),
\r
217 isMulti = 1 < scope.length,// 要素をマージする必要がある
\r
220 isAll, isNot, hasRoot,
\r
223 merge, // 要素がコメントノードで汚染されている場合使う
\r
224 combinator, selector, name, tagName,
\r
225 uid, tmp, xnode, filter, key, op, val, toLower, useName,
\r
226 links, className, attr, flag;
\r
229 if( X.Dom.readyState < X.Dom.Event.XDOM_READY ){
\r
230 alert( 'not ready! X.Dom.listen( X.Dom.Event.XDOM_READY, callback )' );
\r
236 if( typeof queryString !== 'string' ) return ret;
\r
241 for( ; queryString.length; ){
\r
242 console.log( 'queryString[' + queryString + ']' );
\r
246 parsed = X.Dom.Query._parse( queryString );
\r
248 if( typeof parsed === 'number' ){
\r
253 queryString = queryString.substr( parsed[ 0 ] );
\r
254 parsed = parsed[ 1 ];
\r
256 console.log( 'X.Dom.Query._parse ' + parsed );
\r
258 if( parsed === 5 ){
\r
261 xnodes && xnodes.length && ARY_PUSH.apply( ret, xnodes );
\r
269 combinator = parsed[ 0 ];
\r
270 selector = parsed[ 1 ];
\r
271 name = parsed[ 2 ];
\r
272 tagName = selector === 1 ? ( isXML ? name : name.toUpperCase() ) : '*';
\r
273 isAll = tagName === '*';
\r
276 if( !xnodes.length ){
\r
280 if( combinator !== 0 ){
\r
283 console.log( 'cobinator !== 0 ' + parents.length + ' : ' + xnodes.length );
\r
288 l = parents.length;
\r
290 isMulti = isMulti || 1 < l;
\r
292 //console.log( 'combinator ' + combinator );
\r
294 switch( combinator ){
\r
297 for( ; i < l; ++i ){
\r
298 for( xnode = parents[ i ].firstChild(); xnode; xnode = xnode.nextNode() ){
\r
299 if( xnode._xnodeType === 1 && ( isAll || tagName === xnode._tag ) ) xnodes[ ++n ] = xnode;
\r
305 for( ; i < l; ++i ){
\r
306 for( xnode = parents[ i ].nextNode(); xnode; xnode = xnode.nextNode() ){
\r
307 if( xnode._xnodeType === 1 ){
\r
308 if( isAll || tagName === xnode._tag ) xnodes[ ++n ] = xnode;
\r
317 for( ; i < l; ++i ){
\r
318 for( xnode = parents[ i ].nextNode(); xnode; xnode = xnode.nextNode() ){
\r
319 if( xnode._xnodeType === 1 && ( isAll || tagName === xnode._tag ) ){
\r
321 if( merge[ uid ] ){
\r
324 merge[ uid ] = true;
\r
325 xnodes[ ++n ] = xnode;
\r
333 if( combinator === 1 || ( isStart && selector < 7 ) ){
\r
334 console.log( l + ' > ' + xnodes.length + ' tag:' + tagName );
\r
335 for( ; i < l; ++i ){
\r
336 xnode = parents[ i ];
\r
337 xnode._xnodes && xnode._xnodes.length && X.Dom.Query._fetchElements( xnodes, xnode, isAll ? null : tagName );
\r
339 console.log( l + ' >> ' + xnodes.length + ' tag:' + tagName );
\r
345 //alert( 'pre-selector:' + ( xnodes && xnodes.length ) )
\r
347 switch( selector ){
\r
350 filter = [ 'id', 1, name ]; break;
\r
353 filter = [ 'class', 3 /*'~='*/, name ]; break;
\r
356 if( !( filter = X.Dom.Query._filter[ name ] ) ){
\r
362 filter = [ name, parsed[ 3 ], parsed[ 4 ] ]; break;
\r
366 parsed = parsed[ 2 ];
\r
370 xnodes = scope; break;
\r
374 xnodes = [ HTML ]; break;
\r
377 if( links = document.links ){
\r
378 for( xnodes = [], i = links.length; i; ){
\r
379 xnodes[ --i ] = new Node( links[ i ] );
\r
382 // area[href],a[href]
\r
386 if( filter && xnodes.length ){
\r
395 parsed[ 3 ], parsed[ 4 ]
\r
399 if( typeof filter === 'function' ){
\r
401 for( i = 0, n = -1; xnode = xnodes[ i ]; ++i ){
\r
402 if( ( !!filter( xnode ) ) ^ isNot ) tmp[ ++n ] = xnode;
\r
412 key = X.Dom.Attr.renameForTag[ key ] || key;
\r
415 if( !isXML && key === 'class' && op === 3 ){
\r
417 for( i = 0, n = -1; xnode = xnodes[ i ]; ++i ){
\r
418 className = xnode._className;
\r
419 if( !!( className && ( _ + className + _ ).indexOf( val ) > -1 ) ^ isNot ) tmp[ ++n ] = xnode;
\r
424 // flag_call = ($.browser.safari && key === 'selected');
\r
425 // getAttributeを使わない
\r
426 useName = X.UA.IE && key !== 'href' && key !== 'src';
\r
427 toLower = !!val && !isXML && noLower.indexOf( key ) === -1; //!noLower.test(key);
\r
428 if( toLower ) val = val.toLowerCase();
\r
429 if( op === 3 ) val = _ + val + _;
\r
431 for( i = 0, n = -1, l = xnodes.length; i < l; ++i ){
\r
432 xnode = xnodes[ i ];
\r
434 key === 'id' ? xnode._id :
\r
435 key === 'class' ? xnode._className :
\r
436 xnode._attrs && xnode._attrs[ key ];
\r
438 // funcAttr( elem, key ) :
\r
440 // elem[ X.Dom.Attr.renameForDOM[ key ] || key ] :
\r
441 // elem.getAttribute( key, 2 );
\r
442 flag = attr != null;// && ( !useName || attr !== '' );
\r
444 if( toLower ) attr = attr.toLowerCase();
\r
448 flag = attr === val;
\r
451 flag = attr !== val;
\r
454 flag = ( _ + attr + _ ).indexOf( val ) !== -1;
\r
457 flag = attr.indexOf( val ) === 0;
\r
460 flag = attr.lastIndexOf( val ) + val.length === attr.length;
\r
463 flag = attr.indexOf( val ) !== -1;
\r
466 flag = attr === val || attr.substring( 0, val.length + 1 ) === val + '-';
\r
470 if( !!flag ^ isNot ) tmp[ ++n ] = xnode;
\r
480 console.log( '//end :' + ( xnodes && xnodes.length ) );
\r
482 console.log( 'multi:' + ( xnodes && xnodes.length ) );
\r
484 // tree 順に並び替え、同一要素の排除
\r
486 xnodes && xnodes.length && ARY_PUSH.apply( ret, xnodes );
\r
488 if( l < 2 ) return ret[ 0 ] || Node.none;
\r
492 for( i = 0, n = -1; i < l; ++i ){
\r
493 //alert( 'multi:' + i )
\r
495 if( !merge[ uid = xnode._uid ] ){
\r
496 merge[ uid ] = true;
\r
497 xnodes[ ++n ] = xnode;
\r
500 X.Dom.Query._sortElementOrder( ret = [], xnodes, hasRoot ? [ HTML ] : HTML._xnodes );
\r
504 return xnodes.length === 1 ? xnodes[ 0 ] : new X.Dom.NodeList( xnodes );
\r
507 X.Dom.Query._sortElementOrder = function( newList, list, xnodes ){
\r
508 var l = xnodes.length,
\r
511 for( ; i < l; ++i ){
\r
512 child = xnodes[ i ];
\r
513 if( child._xnodeType !== 1 ) continue;
\r
514 //console.log( child._tag );
\r
515 if( ( j = list.indexOf( child ) ) !== -1 ){
\r
516 newList[ newList.length ] = child;
\r
517 list.splice( j, 1 );
\r
518 if( list.length === 1 ){
\r
519 newList[ newList.length ] = list[ 0 ];
\r
523 if( list.length === 0 ) return true;
\r
525 if( ( _xnodes = child._xnodes ) && X.Dom.Query._sortElementOrder( newList, list, _xnodes ) ){
\r
531 X.Dom.Query._fetchElements = function( list, parent, tag ){
\r
532 var xnodes = parent._xnodes,
\r
536 for( ; i < l; ++i ){
\r
537 child = xnodes[ i ];
\r
538 if( child._xnodeType === 1 ){
\r
539 ( !tag || child._tag === tag ) && ( list[ list.length ] = child );
\r
540 //console.log( parent._tag + ' > ' + child._tag + ' == ' + tag+ ' l:' + list.length );
\r
541 child._xnodes && child._xnodes.length && X.Dom.Query._fetchElements( list, child, tag );
\r
546 X.Dom.Query._funcSelectorChild = function( type, flag_all, flags, xnodes ){
\r
548 flag_not = flags.not,
\r
549 i = 0, n = -1, xnode, node,
\r
551 for( ; xnode = xnodes[ i ]; ++i ){
\r
552 tagName = flag_all || xnode._tag;
\r
554 if( /* tmp === null && */ type <= 0 ){
\r
555 for( node = xnode.prevNode(); node; node = node.prevNode() ){
\r
556 if( node._xnodeType === 1 && ( flag_all || tagName === node._tag ) ){
\r
562 if( tmp === null && 0 <= type ){
\r
563 for( node = xnode.nextNode(); node; node = node.nextNode() ){
\r
564 if( node._xnodeType === 1 && ( flag_all || tagName === node._tag ) ){
\r
570 if( tmp === null ) tmp = true;
\r
571 if( tmp ^ flag_not ) res[ ++n ] = xnode;
\r
575 X.Dom.Query._funcSelectorNth = function( pointer, sibling, flag_all, flags, xnodes, a, b ){
\r
576 var _data = funcData,
\r
579 flag_not = flags.not,
\r
580 i = 0, n = -1, uid,
\r
581 c, xnode, tmp, node, tagName;
\r
582 for( ; xnode = xnodes[ i ]; ++i ){
\r
584 tmp = checked[ uid ];
\r
585 if( tmp === void 0 ){
\r
586 for( c = 0, node = xnode.parent[ pointer ](), tagName = flag_all || xnode._tag; node; node = node[ sibling ]() ){
\r
587 if( node._xnodeType === 1 && ( flag_all || tagName === node._tag ) ){
\r
589 checked[ node._uid ] = a === 0 ? c === b : (c - b) % a === 0 && (c - b) / a >= 0;
\r
592 tmp = checked[ uid ];
\r
594 if( tmp ^ flag_not ) res[ ++n ] = xnode;
\r
598 X.Dom.Query._funcSelectorProp = function( prop, flag, flags, xnodes ){
\r
600 flag_not = flag ? flags.not : !flags.not,
\r
601 i = 0, n = -1, xnode;
\r
602 for( ; xnode = xnodes[ i ]; ++i ){
\r
603 if( xnode._attrs && xnode._attrs[ prop ] ^ flag_not ) res[ ++n ] = xnode;
\r
608 X.Dom.Query._filter = {
\r
609 root : function( elem ){
\r
610 return elem === ( elem.ownerDocument || elem.document ).documentElement;
\r
613 m : function( flags, xnodes ){
\r
615 hash = location.hash.slice( 1 ),
\r
616 flag_not = flags.not,
\r
617 i = 0, n = -1, xnode;
\r
618 for ( ; xnode = xnodes[ i ]; ++i ){
\r
619 if( ( ( xnode._id || xnode._attrs && xnode._attrs.name ) === hash ) ^ flag_not ) res[ ++n ] = xnode;
\r
625 m : function( flags, xnodes ){ return X.Dom.Query._funcSelectorChild( -1, true, flags, xnodes ); }
\r
628 m : function( flags, xnodes ){ return X.Dom.Query._funcSelectorChild( 1, true, flags, xnodes ); }
\r
631 m : function( flags, xnodes ){ return X.Dom.Query._funcSelectorChild( 0, true, flags, xnodes ); }
\r
633 'first-of-type' : {
\r
634 m : function( flags, xnodes ){ return X.Dom.Query._funcSelectorChild( -1, false, flags, xnodes ); }
\r
637 m : function( flags, xnodes ){ return X.Dom.Query._funcSelectorChild( 1, false, flags, xnodes ); }
\r
640 m : function( flags, xnodes ){ return X.Dom.Query._funcSelectorChild( 0, false, flags, xnodes ); }
\r
643 m : function( flags, xnodes, a, b ){ return X.Dom.Query._funcSelectorNth( 'firstChild', 'nextNode', true, flags, xnodes, a, b ); }
\r
645 'nth-last-child' : {
\r
646 m : function( flags, xnodes, a, b ){ return X.Dom.Query._funcSelectorNth( 'lastChild', 'prevNode', true, flags, xnodes, a, b ); }
\r
649 m : function( flags, xnodes, a, b ){ return X.Dom.Query._funcSelectorNth( 'firstChild', 'nextNode', false, flags, xnodes, a, b ); }
\r
651 'nth-last-of-type' : {
\r
652 m : function( flags, xnodes, a, b ){ return X.Dom.Query._funcSelectorNth( 'lastChild', 'prevNode', false, flags, xnodes, a, b ); }
\r
655 m : function( flags, xnodes ){
\r
657 flag_not = flags.not,
\r
658 i = 0, n = -1, xnode, tmp, node;
\r
659 for( ; xnode = xnodes[i]; ++i ){
\r
661 for( node = xnode.firstChild(); node; node = node.nextSibling() ){
\r
662 if( node._xnodeType === 1 || ( node._xnodeType === 3 && node._text ) ){
\r
667 if( tmp ^ flag_not ) res[ ++n ] = xnode;
\r
673 m : function( flags, xnodes ){
\r
674 var links = ( xnodes[ 0 ].ownerDocument || xnodes[ 0 ].document ).links,
\r
677 checked, flag_not, i, link, xnode, n;
\r
678 if( !links ) return res;
\r
680 flag_not = flags.not;
\r
681 for( i = 0; link = links[ i ]; ++i ){
\r
682 checked[ ( new Node( link ) )._uid ] = true;
\r
684 for( i = 0, n = -1; xnode = xnodes[ i ]; ++i ){
\r
685 if( checked[ xnode._uid ] ^ flag_not ) res[ ++n ] = xnode;
\r
691 m : function( flags, xnodes, arg ){
\r
693 //reg = new RegExp('^' + arg, 'i'),
\r
694 flag_not = flags.not,
\r
695 i = 0, n = -1, xnode, tmp, lang;
\r
696 arg = arg.toLowerCase();
\r
697 for( ; tmp = xnode = xnodes[ i ]; ++i ){
\r
698 while( tmp && !( lang = tmp._attrs && tmp._attrs[ 'lang' ] ) ){
\r
701 //tmp = !!(tmp && reg.test(tmp.getAttribute('lang')));
\r
702 if( ( !!lang && lang.toLowerCase().indexOf( arg ) === 0 ) ^ flag_not ) res[ ++n ] = xnode;
\r
708 m : function( flags, xnodes ){ return X.Dom.Query._funcSelectorProp( 'disabled', false, flags, xnodes ); }
\r
711 m : function( flags, xnodes ){ return X.Dom.Query._funcSelectorProp( 'disabled', true, flags, xnodes ); }
\r
714 m : function( flags, xnodes ){ return X.Dom.Query._funcSelectorProp( 'checked', true, flags, xnodes ); }
\r
717 m : function( flags, xnodes, arg ){
\r
719 flag_not = flags.not,
\r
720 i = 0, n = -1, xnode;
\r
721 for( ; xnode = xnodes[ i ]; ++i ){
\r
722 if ( ( -1 < ( xnode.text() ).indexOf( arg ) ) ^ flag_not ) res[ ++n ] = xnode;
\r