[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/selector-css2/ -> selector-css2.js (source)

   1  /*
   2  YUI 3.17.2 (build 9c3c78e)
   3  Copyright 2014 Yahoo! Inc. All rights reserved.
   4  Licensed under the BSD License.
   5  http://yuilibrary.com/license/
   6  */
   7  
   8  YUI.add('selector-css2', function (Y, NAME) {
   9  
  10  /**
  11   * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
  12   * @module dom
  13   * @submodule selector-css2
  14   * @for Selector
  15   */
  16  
  17  /*
  18   * Provides helper methods for collecting and filtering DOM elements.
  19   */
  20  
  21  var PARENT_NODE = 'parentNode',
  22      TAG_NAME = 'tagName',
  23      ATTRIBUTES = 'attributes',
  24      COMBINATOR = 'combinator',
  25      PSEUDOS = 'pseudos',
  26  
  27      Selector = Y.Selector,
  28  
  29      SelectorCSS2 = {
  30          _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/,
  31          SORT_RESULTS: true,
  32  
  33          // TODO: better detection, document specific
  34          _isXML: (function() {
  35              var isXML = (Y.config.doc.createElement('div').tagName !== 'DIV');
  36              return isXML;
  37          }()),
  38  
  39          /**
  40           * Mapping of shorthand tokens to corresponding attribute selector
  41           * @property shorthand
  42           * @type object
  43           */
  44          shorthand: {
  45              '\\#(-?[_a-z0-9]+[-\\w\\uE000]*)': '[id=$1]',
  46              '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
  47          },
  48  
  49          /**
  50           * List of operators and corresponding boolean functions.
  51           * These functions are passed the attribute and the current node's value of the attribute.
  52           * @property operators
  53           * @type object
  54           */
  55          operators: {
  56              '': function(node, attr) { return Y.DOM.getAttribute(node, attr) !== ''; }, // Just test for existence of attribute
  57              '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
  58              '|=': '^{val}-?' // optional hyphen-delimited
  59          },
  60  
  61          pseudos: {
  62             'first-child': function(node) {
  63                  return Y.DOM._children(node[PARENT_NODE])[0] === node;
  64              }
  65          },
  66  
  67          _bruteQuery: function(selector, root, firstOnly) {
  68              var ret = [],
  69                  nodes = [],
  70                  visited,
  71                  tokens = Selector._tokenize(selector),
  72                  token = tokens[tokens.length - 1],
  73                  rootDoc = Y.DOM._getDoc(root),
  74                  child,
  75                  id,
  76                  className,
  77                  tagName,
  78                  isUniversal;
  79  
  80              if (token) {
  81                  // prefilter nodes
  82                  id = token.id;
  83                  className = token.className;
  84                  tagName = token.tagName || '*';
  85  
  86                  if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
  87                      // try ID first, unless no root.all && root not in document
  88                      // (root.all works off document, but not getElementById)
  89                      if (id && (root.all || (root.nodeType === 9 || Y.DOM.inDoc(root)))) {
  90                          nodes = Y.DOM.allById(id, root);
  91                      // try className
  92                      } else if (className) {
  93                          nodes = root.getElementsByClassName(className);
  94                      } else { // default to tagName
  95                          nodes = root.getElementsByTagName(tagName);
  96                      }
  97  
  98                  } else { // brute getElementsByTagName()
  99                      visited = [];
 100                      child = root.firstChild;
 101                      isUniversal = tagName === "*";
 102                      while (child) {
 103                          while (child) {
 104                              // IE 6-7 considers comment nodes as element nodes, and gives them the tagName "!".
 105                              // We can filter them out by checking if its tagName is > "@".
 106                              // This also avoids a superflous nodeType === 1 check.
 107                              if (child.tagName > "@" && (isUniversal || child.tagName === tagName)) {
 108                                  nodes.push(child);
 109                              }
 110  
 111                              // We may need to traverse back up the tree to find more unvisited subtrees.
 112                              visited.push(child);
 113                              child = child.firstChild;
 114                          }
 115  
 116                          // Find the most recently visited node who has a next sibling.
 117                          while (visited.length > 0 && !child) {
 118                              child = visited.pop().nextSibling;
 119                          }
 120                      }
 121                  }
 122  
 123                  if (nodes.length) {
 124                      ret = Selector._filterNodes(nodes, tokens, firstOnly);
 125                  }
 126              }
 127  
 128              return ret;
 129          },
 130  
 131          _filterNodes: function(nodes, tokens, firstOnly) {
 132              var i = 0,
 133                  j,
 134                  len = tokens.length,
 135                  n = len - 1,
 136                  result = [],
 137                  node = nodes[0],
 138                  tmpNode = node,
 139                  getters = Y.Selector.getters,
 140                  operator,
 141                  combinator,
 142                  token,
 143                  path,
 144                  pass,
 145                  value,
 146                  tests,
 147                  test;
 148  
 149              for (i = 0; (tmpNode = node = nodes[i++]);) {
 150                  n = len - 1;
 151                  path = null;
 152  
 153                  testLoop:
 154                  while (tmpNode && tmpNode.tagName) {
 155                      token = tokens[n];
 156                      tests = token.tests;
 157                      j = tests.length;
 158                      if (j && !pass) {
 159                          while ((test = tests[--j])) {
 160                              operator = test[1];
 161                              if (getters[test[0]]) {
 162                                  value = getters[test[0]](tmpNode, test[0]);
 163                              } else {
 164                                  value = tmpNode[test[0]];
 165                                  if (test[0] === 'tagName' && !Selector._isXML) {
 166                                      value = value.toUpperCase();
 167                                  }
 168                                  if (typeof value != 'string' && value !== undefined && value.toString) {
 169                                      value = value.toString(); // coerce for comparison
 170                                  } else if (value === undefined && tmpNode.getAttribute) {
 171                                      // use getAttribute for non-standard attributes
 172                                      value = tmpNode.getAttribute(test[0], 2); // 2 === force string for IE
 173                                  }
 174                              }
 175  
 176                              if ((operator === '=' && value !== test[2]) ||  // fast path for equality
 177                                  (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
 178                                  operator.test && !operator.test(value)) ||  // regex test
 179                                  (!operator.test && // protect against RegExp as function (webkit)
 180                                          typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
 181  
 182                                  // skip non element nodes or non-matching tags
 183                                  if ((tmpNode = tmpNode[path])) {
 184                                      while (tmpNode &&
 185                                          (!tmpNode.tagName ||
 186                                              (token.tagName && token.tagName !== tmpNode.tagName))
 187                                      ) {
 188                                          tmpNode = tmpNode[path];
 189                                      }
 190                                  }
 191                                  continue testLoop;
 192                              }
 193                          }
 194                      }
 195  
 196                      n--; // move to next token
 197                      // now that we've passed the test, move up the tree by combinator
 198                      if (!pass && (combinator = token.combinator)) {
 199                          path = combinator.axis;
 200                          tmpNode = tmpNode[path];
 201  
 202                          // skip non element nodes
 203                          while (tmpNode && !tmpNode.tagName) {
 204                              tmpNode = tmpNode[path];
 205                          }
 206  
 207                          if (combinator.direct) { // one pass only
 208                              path = null;
 209                          }
 210  
 211                      } else { // success if we made it this far
 212                          result.push(node);
 213                          if (firstOnly) {
 214                              return result;
 215                          }
 216                          break;
 217                      }
 218                  }
 219              }
 220              node = tmpNode = null;
 221              return result;
 222          },
 223  
 224          combinators: {
 225              ' ': {
 226                  axis: 'parentNode'
 227              },
 228  
 229              '>': {
 230                  axis: 'parentNode',
 231                  direct: true
 232              },
 233  
 234  
 235              '+': {
 236                  axis: 'previousSibling',
 237                  direct: true
 238              }
 239          },
 240  
 241          _parsers: [
 242              {
 243                  name: ATTRIBUTES,
 244                  re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
 245                  fn: function(match, token) {
 246                      var operator = match[2] || '',
 247                          operators = Selector.operators,
 248                          escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
 249                          test;
 250  
 251                      // add prefiltering for ID and CLASS
 252                      if ((match[1] === 'id' && operator === '=') ||
 253                              (match[1] === 'className' &&
 254                              Y.config.doc.documentElement.getElementsByClassName &&
 255                              (operator === '~=' || operator === '='))) {
 256                          token.prefilter = match[1];
 257  
 258  
 259                          match[3] = escVal;
 260  
 261                          // escape all but ID for prefilter, which may run through QSA (via Dom.allById)
 262                          token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
 263  
 264                      }
 265  
 266                      // add tests
 267                      if (operator in operators) {
 268                          test = operators[operator];
 269                          if (typeof test === 'string') {
 270                              match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
 271                              test = new RegExp(test.replace('{val}', match[3]));
 272                          }
 273                          match[2] = test;
 274                      }
 275                      if (!token.last || token.prefilter !== match[1]) {
 276                          return match.slice(1);
 277                      }
 278                  }
 279              },
 280              {
 281                  name: TAG_NAME,
 282                  re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
 283                  fn: function(match, token) {
 284                      var tag = match[1];
 285  
 286                      if (!Selector._isXML) {
 287                          tag = tag.toUpperCase();
 288                      }
 289  
 290                      token.tagName = tag;
 291  
 292                      if (tag !== '*' && (!token.last || token.prefilter)) {
 293                          return [TAG_NAME, '=', tag];
 294                      }
 295                      if (!token.prefilter) {
 296                          token.prefilter = 'tagName';
 297                      }
 298                  }
 299              },
 300              {
 301                  name: COMBINATOR,
 302                  re: /^\s*([>+~]|\s)\s*/,
 303                  fn: function(match, token) {
 304                  }
 305              },
 306              {
 307                  name: PSEUDOS,
 308                  re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
 309                  fn: function(match, token) {
 310                      var test = Selector[PSEUDOS][match[1]];
 311                      if (test) { // reorder match array and unescape special chars for tests
 312                          if (match[2]) {
 313                              match[2] = match[2].replace(/\\/g, '');
 314                          }
 315                          return [match[2], test];
 316                      } else { // selector token not supported (possibly missing CSS3 module)
 317                          return false;
 318                      }
 319                  }
 320              }
 321              ],
 322  
 323          _getToken: function(token) {
 324              return {
 325                  tagName: null,
 326                  id: null,
 327                  className: null,
 328                  attributes: {},
 329                  combinator: null,
 330                  tests: []
 331              };
 332          },
 333  
 334          /*
 335              Break selector into token units per simple selector.
 336              Combinator is attached to the previous token.
 337           */
 338          _tokenize: function(selector) {
 339              selector = selector || '';
 340              selector = Selector._parseSelector(Y.Lang.trim(selector));
 341              var token = Selector._getToken(),     // one token per simple selector (left selector holds combinator)
 342                  query = selector, // original query for debug report
 343                  tokens = [],    // array of tokens
 344                  found = false,  // whether or not any matches were found this pass
 345                  match,         // the regex match
 346                  test,
 347                  i, parser;
 348  
 349              /*
 350                  Search for selector patterns, store, and strip them from the selector string
 351                  until no patterns match (invalid selector) or we run out of chars.
 352  
 353                  Multiple attributes and pseudos are allowed, in any order.
 354                  for example:
 355                      'form:first-child[type=button]:not(button)[lang|=en]'
 356              */
 357              outer:
 358              do {
 359                  found = false; // reset after full pass
 360                  for (i = 0; (parser = Selector._parsers[i++]);) {
 361                      if ( (match = parser.re.exec(selector)) ) { // note assignment
 362                          if (parser.name !== COMBINATOR ) {
 363                              token.selector = selector;
 364                          }
 365                          selector = selector.replace(match[0], ''); // strip current match from selector
 366                          if (!selector.length) {
 367                              token.last = true;
 368                          }
 369  
 370                          if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
 371                              match[1] = Selector._attrFilters[match[1]];
 372                          }
 373  
 374                          test = parser.fn(match, token);
 375                          if (test === false) { // selector not supported
 376                              found = false;
 377                              break outer;
 378                          } else if (test) {
 379                              token.tests.push(test);
 380                          }
 381  
 382                          if (!selector.length || parser.name === COMBINATOR) {
 383                              tokens.push(token);
 384                              token = Selector._getToken(token);
 385                              if (parser.name === COMBINATOR) {
 386                                  token.combinator = Y.Selector.combinators[match[1]];
 387                              }
 388                          }
 389                          found = true;
 390                      }
 391                  }
 392              } while (found && selector.length);
 393  
 394              if (!found || selector.length) { // not fully parsed
 395                  tokens = [];
 396              }
 397              return tokens;
 398          },
 399  
 400          _replaceMarkers: function(selector) {
 401              selector = selector.replace(/\[/g, '\uE003');
 402              selector = selector.replace(/\]/g, '\uE004');
 403  
 404              selector = selector.replace(/\(/g, '\uE005');
 405              selector = selector.replace(/\)/g, '\uE006');
 406              return selector;
 407          },
 408  
 409          _replaceShorthand: function(selector) {
 410              var shorthand = Y.Selector.shorthand,
 411                  re;
 412  
 413              for (re in shorthand) {
 414                  if (shorthand.hasOwnProperty(re)) {
 415                      selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
 416                  }
 417              }
 418  
 419              return selector;
 420          },
 421  
 422          _parseSelector: function(selector) {
 423              var replaced = Y.Selector._replaceSelector(selector),
 424                  selector = replaced.selector;
 425  
 426              // replace shorthand (".foo, #bar") after pseudos and attrs
 427              // to avoid replacing unescaped chars
 428              selector = Y.Selector._replaceShorthand(selector);
 429  
 430              selector = Y.Selector._restore('attr', selector, replaced.attrs);
 431              selector = Y.Selector._restore('pseudo', selector, replaced.pseudos);
 432  
 433              // replace braces and parens before restoring escaped chars
 434              // to avoid replacing ecaped markers
 435              selector = Y.Selector._replaceMarkers(selector);
 436              selector = Y.Selector._restore('esc', selector, replaced.esc);
 437  
 438              return selector;
 439          },
 440  
 441          _attrFilters: {
 442              'class': 'className',
 443              'for': 'htmlFor'
 444          },
 445  
 446          getters: {
 447              href: function(node, attr) {
 448                  return Y.DOM.getAttribute(node, attr);
 449              },
 450  
 451              id: function(node, attr) {
 452                  return Y.DOM.getId(node);
 453              }
 454          }
 455      };
 456  
 457  Y.mix(Y.Selector, SelectorCSS2, true);
 458  Y.Selector.getters.src = Y.Selector.getters.rel = Y.Selector.getters.href;
 459  
 460  // IE wants class with native queries
 461  if (Y.Selector.useNative && Y.config.doc.querySelector) {
 462      Y.Selector.shorthand['\\.(-?[_a-z]+[-\\w]*)'] = '[class~=$1]';
 463  }
 464  
 465  
 466  }, '3.17.2', {"requires": ["selector-native"]});


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1