testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Selector.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /***
     3 MochiKit.Selector 1.4.2
     5 See <http://mochikit.com/> for documentation, downloads, license, etc.
     7 (c) 2005 Bob Ippolito and others.  All rights Reserved.
     9 ***/
    11 MochiKit.Base._deps('Selector', ['Base', 'DOM', 'Iter']);
    13 MochiKit.Selector.NAME = "MochiKit.Selector";
    14 MochiKit.Selector.VERSION = "1.4.2";
    16 MochiKit.Selector.__repr__ = function () {
    17     return "[" + this.NAME + " " + this.VERSION + "]";
    18 };
    20 MochiKit.Selector.toString = function () {
    21     return this.__repr__();
    22 };
    24 MochiKit.Selector.EXPORT = [
    25     "Selector",
    26     "findChildElements",
    27     "findDocElements",
    28     "$$"
    29 ];
    31 MochiKit.Selector.EXPORT_OK = [
    32 ];
    34 MochiKit.Selector.Selector = function (expression) {
    35     this.params = {classNames: [], pseudoClassNames: []};
    36     this.expression = expression.toString().replace(/(^\s+|\s+$)/g, '');
    37     this.parseExpression();
    38     this.compileMatcher();
    39 };
    41 MochiKit.Selector.Selector.prototype = {
    42     /***
    44     Selector class: convenient object to make CSS selections.
    46     ***/
    47     __class__: MochiKit.Selector.Selector,
    49     /** @id MochiKit.Selector.Selector.prototype.parseExpression */
    50     parseExpression: function () {
    51         function abort(message) {
    52             throw 'Parse error in selector: ' + message;
    53         }
    55         if (this.expression == '')  {
    56             abort('empty expression');
    57         }
    59         var repr = MochiKit.Base.repr;
    60         var params = this.params;
    61         var expr = this.expression;
    62         var match, modifier, clause, rest;
    63         while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!^$*]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
    64             params.attributes = params.attributes || [];
    65             params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
    66             expr = match[1];
    67         }
    69         if (expr == '*') {
    70             return this.params.wildcard = true;
    71         }
    73         while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+(?:\([^)]*\))?)(.*)/i)) {
    74             modifier = match[1];
    75             clause = match[2];
    76             rest = match[3];
    77             switch (modifier) {
    78                 case '#':
    79                     params.id = clause;
    80                     break;
    81                 case '.':
    82                     params.classNames.push(clause);
    83                     break;
    84                 case ':':
    85                     params.pseudoClassNames.push(clause);
    86                     break;
    87                 case '':
    88                 case undefined:
    89                     params.tagName = clause.toUpperCase();
    90                     break;
    91                 default:
    92                     abort(repr(expr));
    93             }
    94             expr = rest;
    95         }
    97         if (expr.length > 0) {
    98             abort(repr(expr));
    99         }
   100     },
   102     /** @id MochiKit.Selector.Selector.prototype.buildMatchExpression */
   103     buildMatchExpression: function () {
   104         var repr = MochiKit.Base.repr;
   105         var params = this.params;
   106         var conditions = [];
   107         var clause, i;
   109         function childElements(element) {
   110             return "MochiKit.Base.filter(function (node) { return node.nodeType == 1; }, " + element + ".childNodes)";
   111         }
   113         if (params.wildcard) {
   114             conditions.push('true');
   115         }
   116         if (clause = params.id) {
   117             conditions.push('element.id == ' + repr(clause));
   118         }
   119         if (clause = params.tagName) {
   120             conditions.push('element.tagName.toUpperCase() == ' + repr(clause));
   121         }
   122         if ((clause = params.classNames).length > 0) {
   123             for (i = 0; i < clause.length; i++) {
   124                 conditions.push('MochiKit.DOM.hasElementClass(element, ' + repr(clause[i]) + ')');
   125             }
   126         }
   127         if ((clause = params.pseudoClassNames).length > 0) {
   128             for (i = 0; i < clause.length; i++) {
   129                 var match = clause[i].match(/^([^(]+)(?:\((.*)\))?$/);
   130                 var pseudoClass = match[1];
   131                 var pseudoClassArgument = match[2];
   132                 switch (pseudoClass) {
   133                     case 'root':
   134                         conditions.push('element.nodeType == 9 || element === element.ownerDocument.documentElement'); break;
   135                     case 'nth-child':
   136                     case 'nth-last-child':
   137                     case 'nth-of-type':
   138                     case 'nth-last-of-type':
   139                         match = pseudoClassArgument.match(/^((?:(\d+)n\+)?(\d+)|odd|even)$/);
   140                         if (!match) {
   141                             throw "Invalid argument to pseudo element nth-child: " + pseudoClassArgument;
   142                         }
   143                         var a, b;
   144                         if (match[0] == 'odd') {
   145                             a = 2;
   146                             b = 1;
   147                         } else if (match[0] == 'even') {
   148                             a = 2;
   149                             b = 0;
   150                         } else {
   151                             a = match[2] && parseInt(match) || null;
   152                             b = parseInt(match[3]);
   153                         }
   154                         conditions.push('this.nthChild(element,' + a + ',' + b
   155                                         + ',' + !!pseudoClass.match('^nth-last')    // Reverse
   156                                         + ',' + !!pseudoClass.match('of-type$')     // Restrict to same tagName
   157                                         + ')');
   158                         break;
   159                     case 'first-child':
   160                         conditions.push('this.nthChild(element, null, 1)');
   161                         break;
   162                     case 'last-child':
   163                         conditions.push('this.nthChild(element, null, 1, true)');
   164                         break;
   165                     case 'first-of-type':
   166                         conditions.push('this.nthChild(element, null, 1, false, true)');
   167                         break;
   168                     case 'last-of-type':
   169                         conditions.push('this.nthChild(element, null, 1, true, true)');
   170                         break;
   171                     case 'only-child':
   172                         conditions.push(childElements('element.parentNode') + '.length == 1');
   173                         break;
   174                     case 'only-of-type':
   175                         conditions.push('MochiKit.Base.filter(function (node) { return node.tagName == element.tagName; }, ' + childElements('element.parentNode') + ').length == 1');
   176                         break;
   177                     case 'empty':
   178                         conditions.push('element.childNodes.length == 0');
   179                         break;
   180                     case 'enabled':
   181                         conditions.push('(this.isUIElement(element) && element.disabled === false)');
   182                         break;
   183                     case 'disabled':
   184                         conditions.push('(this.isUIElement(element) && element.disabled === true)');
   185                         break;
   186                     case 'checked':
   187                         conditions.push('(this.isUIElement(element) && element.checked === true)');
   188                         break;
   189                     case 'not':
   190                         var subselector = new MochiKit.Selector.Selector(pseudoClassArgument);
   191                         conditions.push('!( ' + subselector.buildMatchExpression() + ')')
   192                         break;
   193                 }
   194             }
   195         }
   196         if (clause = params.attributes) {
   197             MochiKit.Base.map(function (attribute) {
   198                 var value = 'MochiKit.DOM.getNodeAttribute(element, ' + repr(attribute.name) + ')';
   199                 var splitValueBy = function (delimiter) {
   200                     return value + '.split(' + repr(delimiter) + ')';
   201                 }
   202                 conditions.push(value + ' != null');
   203                 switch (attribute.operator) {
   204                     case '=':
   205                         conditions.push(value + ' == ' + repr(attribute.value));
   206                         break;
   207                     case '~=':
   208                         conditions.push('MochiKit.Base.findValue(' + splitValueBy(' ') + ', ' + repr(attribute.value) + ') > -1');
   209                         break;
   210                     case '^=':
   211                         conditions.push(value + '.substring(0, ' + attribute.value.length + ') == ' + repr(attribute.value));
   212                         break;
   213                     case '$=':
   214                         conditions.push(value + '.substring(' + value + '.length - ' + attribute.value.length + ') == ' + repr(attribute.value));
   215                         break;
   216                     case '*=':
   217                         conditions.push(value + '.match(' + repr(attribute.value) + ')');
   218                         break;
   219                     case '|=':
   220                         conditions.push(splitValueBy('-') + '[0].toUpperCase() == ' + repr(attribute.value.toUpperCase()));
   221                         break;
   222                     case '!=':
   223                         conditions.push(value + ' != ' + repr(attribute.value));
   224                         break;
   225                     case '':
   226                     case undefined:
   227                         // Condition already added above
   228                         break;
   229                     default:
   230                         throw 'Unknown operator ' + attribute.operator + ' in selector';
   231                 }
   232             }, clause);
   233         }
   235         return conditions.join(' && ');
   236     },
   238     /** @id MochiKit.Selector.Selector.prototype.compileMatcher */
   239     compileMatcher: function () {
   240         var code = 'return (!element.tagName) ? false : ' +
   241                    this.buildMatchExpression() + ';';
   242         this.match = new Function('element', code);
   243     },
   245     /** @id MochiKit.Selector.Selector.prototype.nthChild */
   246     nthChild: function (element, a, b, reverse, sametag){
   247         var siblings = MochiKit.Base.filter(function (node) {
   248             return node.nodeType == 1;
   249         }, element.parentNode.childNodes);
   250         if (sametag) {
   251             siblings = MochiKit.Base.filter(function (node) {
   252                 return node.tagName == element.tagName;
   253             }, siblings);
   254         }
   255         if (reverse) {
   256             siblings = MochiKit.Iter.reversed(siblings);
   257         }
   258         if (a) {
   259             var actualIndex = MochiKit.Base.findIdentical(siblings, element);
   260             return ((actualIndex + 1 - b) / a) % 1 == 0;
   261         } else {
   262             return b == MochiKit.Base.findIdentical(siblings, element) + 1;
   263         }
   264     },
   266     /** @id MochiKit.Selector.Selector.prototype.isUIElement */
   267     isUIElement: function (element) {
   268         return MochiKit.Base.findValue(['input', 'button', 'select', 'option', 'textarea', 'object'],
   269                 element.tagName.toLowerCase()) > -1;
   270     },
   272     /** @id MochiKit.Selector.Selector.prototype.findElements */
   273     findElements: function (scope, axis) {
   274         var element;
   276         if (axis == undefined) {
   277             axis = "";
   278         }
   280         function inScope(element, scope) {
   281             if (axis == "") {
   282                 return MochiKit.DOM.isChildNode(element, scope);
   283             } else if (axis == ">") {
   284                 return element.parentNode === scope;
   285             } else if (axis == "+") {
   286                 return element === nextSiblingElement(scope);
   287             } else if (axis == "~") {
   288                 var sibling = scope;
   289                 while (sibling = nextSiblingElement(sibling)) {
   290                     if (element === sibling) {
   291                         return true;
   292                     }
   293                 }
   294                 return false;
   295             } else {
   296                 throw "Invalid axis: " + axis;
   297             }
   298         }
   300         if (element = MochiKit.DOM.getElement(this.params.id)) {
   301             if (this.match(element)) {
   302                 if (!scope || inScope(element, scope)) {
   303                     return [element];
   304                 }
   305             }
   306         }
   308         function nextSiblingElement(node) {
   309             node = node.nextSibling;
   310             while (node && node.nodeType != 1) {
   311                 node = node.nextSibling;
   312             }
   313             return node;
   314         }
   316         if (axis == "") {
   317             scope = (scope || MochiKit.DOM.currentDocument()).getElementsByTagName(this.params.tagName || '*');
   318         } else if (axis == ">") {
   319             if (!scope) {
   320                 throw "> combinator not allowed without preceding expression";
   321             }
   322             scope = MochiKit.Base.filter(function (node) {
   323                 return node.nodeType == 1;
   324             }, scope.childNodes);
   325         } else if (axis == "+") {
   326             if (!scope) {
   327                 throw "+ combinator not allowed without preceding expression";
   328             }
   329             scope = nextSiblingElement(scope) && [nextSiblingElement(scope)];
   330         } else if (axis == "~") {
   331             if (!scope) {
   332                 throw "~ combinator not allowed without preceding expression";
   333             }
   334             var newscope = [];
   335             while (nextSiblingElement(scope)) {
   336                 scope = nextSiblingElement(scope);
   337                 newscope.push(scope);
   338             }
   339             scope = newscope;
   340         }
   342         if (!scope) {
   343             return [];
   344         }
   346         var results = MochiKit.Base.filter(MochiKit.Base.bind(function (scopeElt) {
   347             return this.match(scopeElt);
   348         }, this), scope);
   350         return results;
   351     },
   353     /** @id MochiKit.Selector.Selector.prototype.repr */
   354     repr: function () {
   355         return 'Selector(' + this.expression + ')';
   356     },
   358     toString: MochiKit.Base.forwardCall("repr")
   359 };
   361 MochiKit.Base.update(MochiKit.Selector, {
   363     /** @id MochiKit.Selector.findChildElements */
   364     findChildElements: function (element, expressions) {
   365         var uniq = function(arr) {
   366             var res = [];
   367             for (var i = 0; i < arr.length; i++) {
   368                 if (MochiKit.Base.findIdentical(res, arr[i]) < 0) {
   369                     res.push(arr[i]);
   370                 }
   371             }
   372             return res;
   373         };
   374         return MochiKit.Base.flattenArray(MochiKit.Base.map(function (expression) {
   375             var nextScope = "";
   376             var reducer = function (results, expr) {
   377                 if (match = expr.match(/^[>+~]$/)) {
   378                     nextScope = match[0];
   379                     return results;
   380                 } else {
   381                     var selector = new MochiKit.Selector.Selector(expr);
   382                     var elements = MochiKit.Iter.reduce(function (elements, result) {
   383                         return MochiKit.Base.extend(elements, selector.findElements(result || element, nextScope));
   384                     }, results, []);
   385                     nextScope = "";
   386                     return elements;
   387                 }
   388             };
   389             var exprs = expression.replace(/(^\s+|\s+$)/g, '').split(/\s+/);
   390             return uniq(MochiKit.Iter.reduce(reducer, exprs, [null]));
   391         }, expressions));
   392     },
   394     findDocElements: function () {
   395         return MochiKit.Selector.findChildElements(MochiKit.DOM.currentDocument(), arguments);
   396     },
   398     __new__: function () {
   399         var m = MochiKit.Base;
   401         this.$$ = this.findDocElements;
   403         this.EXPORT_TAGS = {
   404             ":common": this.EXPORT,
   405             ":all": m.concat(this.EXPORT, this.EXPORT_OK)
   406         };
   408         m.nameFunctions(this);
   409     }
   410 });
   412 MochiKit.Selector.__new__();
   414 MochiKit.Base._exportSymbols(this, MochiKit.Selector);

mercurial