├── .babelrc.js ├── .gitignore ├── .prettierrc ├── .travis.yml ├── README.md ├── __tests__ └── index.spec.js ├── package.json └── src ├── index.js └── query-selector ├── gen-parser.md ├── parser-grammar.kison ├── parser.js └── util.js /.babelrc.js: -------------------------------------------------------------------------------- 1 | console.log('Load babel config'); 2 | 3 | module.exports = { 4 | presets: [ 5 | [ 6 | '@babel/preset-env', 7 | { 8 | loose: true, 9 | modules: false, 10 | }, 11 | ], 12 | ], 13 | env: { 14 | test: { 15 | presets: ['@babel/preset-env'], 16 | }, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.log 3 | .idea 4 | .ipr 5 | .iws 6 | *~ 7 | ~* 8 | *.diff 9 | *.patch 10 | *.bak 11 | .DS_Store 12 | Thumbs.db 13 | .project 14 | .*proj 15 | .svn 16 | *.swp 17 | *.swo 18 | *.pyc 19 | *.pyo 20 | node_modules 21 | .cache 22 | build 23 | coverage 24 | *.map 25 | /pkg/ 26 | yarn.lock 27 | /storybook-static/ 28 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | notifications: 3 | email: 4 | - yiminghe@gmail.com 5 | node_js: 6 | - 10.15.0 7 | script: 8 | - npm run coverage 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # query-selector 2 | --- 3 | 4 | querySelectorAll in javascript 5 | 6 | [![query-selector](https://nodei.co/npm/query-selector.png)](https://npmjs.org/package/query-selector) 7 | [![NPM downloads](http://img.shields.io/npm/dm/query-selector.svg)](https://npmjs.org/package/query-selector) 8 | [![Build Status](https://secure.travis-ci.org/yiminghe/query-selector.png?branch=master)](https://travis-ci.org/yiminghe/query-selector) 9 | [![Coverage Status](https://img.shields.io/coveralls/yiminghe/query-selector.svg)](https://coveralls.io/r/yiminghe/query-selector?branch=master) 10 | 11 | 12 | ## usage 13 | 14 | ``` 15 | import querySelectorAll from 'query-selector'; // var querySelectorAll = require('query-selector').default; 16 | import {jsdom} from "jsdom"; 17 | var doc = jsdom('
12
'); 18 | var time = Date.now(); 19 | console.log(doc.querySelectorAll('#t span', doc).length); 20 | console.log(doc.querySelectorAll('#t span', doc)[0].innerHTML); 21 | console.log(Date.now()-time); 22 | time = Date.now(); 23 | console.log(querySelectorAll('#t span', doc).length); 24 | console.log(querySelectorAll('#t span', doc)[0].innerHTML); 25 | console.log(Date.now()-time); 26 | ``` 27 | 28 | ## history 29 | 30 | ### 2.0.0 / 2019.08.14 31 | 32 | - use new build tool and test tool 33 | - change export: `var querySelectorAll = require('query-selector').default;` 34 | -------------------------------------------------------------------------------- /__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * css3 selector tc modified from Sizzle 3 | * @author yiminghe@gmail.com 4 | */ 5 | 6 | 7 | import select from '../src/index'; 8 | import $ from 'jquery'; 9 | 10 | var matches = select.matches; 11 | var ieVersion = select.util.ie; 12 | 13 | document.documentElement.id = 'html'; 14 | 15 | function makeArray(arr) { 16 | var ret = []; 17 | for (var i = 0; i < arr.length; i++) { 18 | ret[i] = arr[i]; 19 | } 20 | return ret; 21 | } 22 | 23 | function matchesSelector(el, selector) { 24 | return matches(selector, [el]).length === 1; 25 | } 26 | 27 | function ok(a, name) { 28 | it(name, function () { 29 | if (typeof a === 'function') { 30 | a = a(); 31 | } 32 | expect(!!a).toEqual(true); 33 | }); 34 | } 35 | 36 | function getAttr(el, name) { 37 | var ret = el && el.getAttributeNode(name); 38 | return ret && ret.nodeValue; 39 | } 40 | 41 | /** 42 | * Asserts that a select matches the given IDs 43 | * @param {String} a - Assertion name 44 | * @param {String} b - selector 45 | * @param {String} c - Array of ids to construct what is expected 46 | * @example t("Check for something", "//[a]", ["foo", "baar"]); 47 | * @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar' 48 | */ 49 | function t(a, b, c, only) { 50 | const f = only ? it.only : it; 51 | f(a, function () { 52 | var f = select(b), 53 | s = [], 54 | i = 0; 55 | for (; i < f.length; i++) { 56 | s.push(getAttr(f[i], 'id')); 57 | } 58 | expect(s).toEqual(c); 59 | }); 60 | 61 | } 62 | 63 | /** 64 | * Returns an array of elements with the given IDs 65 | * @example q("main", "foo", "bar") 66 | * @result [
, , ] 67 | */ 68 | function q() { 69 | var r = [], 70 | i = 0; 71 | 72 | for (; i < arguments.length; i++) { 73 | r.push(document.getElementById(arguments[i])); 74 | } 75 | return r; 76 | } 77 | 78 | function broken(name, selector) { 79 | 80 | it(name + ": " + selector, function () { 81 | try { 82 | select(selector); 83 | } catch (e) { 84 | expect(e.message.toLowerCase().indexOf("syntax error")).toBeGreaterThan(-1); 85 | } 86 | }); 87 | } 88 | 89 | function equal(a, b, name) { 90 | it(name, function () { 91 | if (typeof a === 'function') { 92 | a = a(); 93 | } 94 | if (typeof b === 'function') { 95 | b = b(); 96 | } 97 | expect(a).toEqual(b); 98 | }); 99 | } 100 | 101 | var xml = ` 102 | 103 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 1 115 | 116 | 117 | 118 | 119 | foo 120 | 121 | 122 | 123 | 124 | 125 | 126 | `; 127 | 128 | const fixtureHtml = ` 129 |
130 | 131 |

tests

132 | 133 |

134 | 135 |
136 |

137 | 138 |
139 | 140 | 141 | 142 |
143 |
144 |

See this blog entry for more information.

146 | 147 |

148 | Here are some [links] in a normal paragraph: 149 | Google, 150 | Google Groups (Link). 151 | This link has class="blog": 152 | diveintomark 153 | 154 |

155 | 156 |
157 |

Everything inside the red border is inside a div with id="foo".

158 | 159 |

This is a normal link: Yahoo 160 |

161 | 162 |

This link has class="blog": Simon Willison's Weblog

164 | 165 |
166 | 167 | 168 |
169 |
170 |
171 | 172 | 173 | 174 |

Try them out:

175 |
    176 |
    177 | 178 |
    179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 204 | 210 | 217 | 226 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | test element 245 |
    246 | Float test. 247 | 248 |
    249 | 250 | 251 |
    252 |
    253 | 254 |
    255 | 256 | 257 | 258 | 259 |
    260 | 261 |
    262 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 280 | 285 | 288 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 301 | 302 | 303 | 304 | 305 |
    306 |
    307 |
    308 | 314 |
    315 |
    hi there
    316 |
    317 |
    318 | 319 |
    320 |
    321 |
    322 |
    323 |
    324 |
    325 | 326 |
    327 |
    328 | 329 | 330 | 331 |
    332 |
    333 | 334 | 335 |
    336 |
    337 | 338 |
    339 |
      340 |
    1. Rice
    2. 341 |
    3. Beans
    4. 342 |
    5. Blinis
    6. 343 |
    7. Tofu
    8. 344 |
    345 | 346 |
    I'm hungry. I should...
    347 | ...Eat lots of food... | 348 | ...Eat a little food... | 349 | ...Eat no food... 350 | ...Eat a burger... 351 | ...Eat some funyuns... 352 | ...Eat some funyuns... 353 |
    354 | 355 |
    356 | 357 | 358 |
    359 | 360 |
    361 | 1 362 | 2 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 |
    372 | ​ 373 |
    374 |
    375 |
    376 |
    377 | `; 378 | 379 | $('body').append(fixtureHtml); 380 | 381 | var createWithFriesXML = function () { 382 | return $.parseXML(xml); 383 | }; 384 | 385 | describe("element", function () { 386 | 387 | document.body.id = 'body'; 388 | 389 | var form = document.getElementById("form"); 390 | 391 | it('fix href normalization', function () { 392 | expect(select('a[href="test.html"]').length).toEqual(1); 393 | }); 394 | 395 | it('Select all', function () { 396 | 397 | expect(select("*").length).toBeGreaterThan(30); 398 | }); 399 | 400 | it('Select all elements, no comment nodes', function () { 401 | var all = select("*"), good = true; 402 | for (var i = 0; i < all.length; i++) { 403 | if (all[i].nodeType === 8) { 404 | good = false; 405 | } 406 | } 407 | expect(good).toEqual(true); 408 | }); 409 | 410 | t("Element Selector html", "html", ["html"]); 411 | t("Element Selector body", "body", ["body"]); 412 | t("Element Selector p", "#qunit-fixture p", ["firstp", "ap", "sndp", "en", "sap", "first"]); 413 | 414 | t("Parent Element", "dl ol", ["empty", "listWithTabIndex"]); 415 | t("Parent Element (non-space descendant combinator)", "dl\tol", ["empty", "listWithTabIndex"]); 416 | 417 | it("Object/param as context", function () { 418 | var obj1 = document.getElementById("object1"); 419 | expect(select("param", obj1).length).toEqual(2); 420 | }); 421 | 422 | it("Finding selects with a context.", function () { 423 | expect(select("select", form)).toEqual(q("select1", "select2", "select3", "select4", "select5")); 424 | }); 425 | 426 | // Check for unique-ness and sort order 427 | it("Check for duplicates: p, div p", function () { 428 | expect(select("p, div p")).toEqual(select('p')); 429 | }); 430 | 431 | t("Checking sort order -1", "#fixture h2, #fixture h1", ["qunit-header", "qunit-banner", "qunit-userAgent"]); 432 | t("Checking sort order -2", "#qunit-fixture p, #qunit-fixture p a", ["firstp", "simon1", "ap", "google", "groups", "anchor1", "mark", "sndp", "en", "yahoo", "sap", "anchor2", "simon", "first"]); 433 | 434 | // Test Conflict ID 435 | var lengthtest = document.getElementById("lengthtest"); 436 | 437 | it("Finding element with id of ID. -1", function () { 438 | expect(select("#idTest", lengthtest)).toEqual(q('idTest')); 439 | }); 440 | 441 | it("Finding element with id of ID. -2", function () { 442 | expect(select("[name='id']", lengthtest)).toEqual(q('idTest')); 443 | }); 444 | 445 | it("Finding elements with id of ID.", function () { 446 | expect(select("input[id='idTest']", lengthtest)).toEqual(q('idTest')); 447 | }); 448 | 449 | var siblingTest = document.getElementById("siblingTest"); 450 | it("Element-rooted QSA select based on document context", function () { 451 | expect(select("div em", siblingTest)).toEqual(q('siblingfirst', 452 | 'siblingnext', 453 | 'siblingthird', 454 | 'siblingchild', 455 | 'siblinggrandchild', 456 | 'siblinggreatgrandchild')); 457 | }); 458 | 459 | it("Other document as context", function () { 460 | var iframe = document.getElementById("iframe"), 461 | iframeDoc = iframe.contentDocument || iframe.contentWindow.document; 462 | iframeDoc.open(); 463 | iframeDoc.write("

    bar

    "); 464 | iframeDoc.close(); 465 | expect(select("p#foo", iframeDoc)).toEqual([iframeDoc.getElementById("foo")]); 466 | }); 467 | 468 | it("No stack or performance problems with large amounts of descendents", function () { 469 | var html = ""; 470 | for (var i = 0; i < 100; i++) { 471 | html = "
    " + html + "
    "; 472 | } 473 | html = $(html).appendTo(document.body); 474 | 475 | expect(select("body div div div").length).toBeGreaterThan(0); 476 | expect(select("body>div div div").length).toBeGreaterThan(0); 477 | 478 | html.remove(); 479 | }); 480 | 481 | it('', function () { 482 | q("qunit-fixture")[0].appendChild(document.createElement("toString")).id = "toString"; 483 | }); 484 | 485 | t("Element name matches Object.prototype property", "toString#toString", ["toString"]); 486 | 487 | it('', function () { 488 | $('#toString').remove(); 489 | }); 490 | }); 491 | 492 | describe("XML Document Selectors", function () { 493 | var xml = createWithFriesXML(); 494 | 495 | it("Element Selector with underscore", function () { 496 | expect(select("foo_bar", xml).length).toEqual(1); 497 | }); 498 | 499 | it("Class selector", function () { 500 | expect(select(".component", xml).length).toEqual(1); 501 | }); 502 | 503 | it("Attribute selector for class", function () { 504 | expect(select("component[class*=component]", xml).length).toEqual(1); 505 | }); 506 | 507 | it("Attribute selector with name", function () { 508 | expect(select("property[name=prop2]", xml).length).toEqual(1); 509 | }); 510 | 511 | it("Attribute selector with name -2", function () { 512 | expect(select("[name=prop2]", xml).length).toEqual(1); 513 | }); 514 | 515 | it("Attribute selector with ID", function () { 516 | expect(select("#seite1", xml).length).toEqual(1); 517 | }); 518 | 519 | it("Attribute selector with ID -2", function () { 520 | expect(select("component#seite1", xml).length).toEqual(1); 521 | }); 522 | 523 | it("Attribute selector filter with ID", function () { 524 | expect(matches("#seite1", select("component", xml)).length).toEqual(1); 525 | }); 526 | 527 | it("Descendent selector and dir caching", function () { 528 | expect(select("meta property thing", select("component", xml)[0]).length).toEqual(2); 529 | }); 530 | 531 | it("Check for namespaced element", function () { 532 | expect(matches("soap\\:Envelope", [xml.lastChild]).length).toEqual(1); 533 | }); 534 | }); 535 | 536 | describe("broken", function () { 537 | 538 | broken("Broken Selector", "["); 539 | broken("Broken Selector", "("); 540 | broken("Broken Selector", "{"); 541 | broken("Broken Selector", "<"); 542 | broken("Broken Selector", "()"); 543 | broken("Broken Selector", "<>"); 544 | broken("Broken Selector", "{}"); 545 | broken("Broken Selector", ","); 546 | broken("Broken Selector", ",a"); 547 | broken("Broken Selector", "a,"); 548 | // Hangs on IE 9 if regular expression is inefficient 549 | broken("Broken Selector", "[id=012345678901234567890123456789"); 550 | broken("Doesn't exist", ":visble"); 551 | broken("Nth-child", ":nth-child"); 552 | 553 | broken("Nth-child", ":nth-child(2n+-0)"); 554 | broken("Nth-child", ":nth-child(2+0)"); 555 | broken("Nth-child", ":nth-child(- 1n)"); 556 | broken("Nth-child", ":nth-child(-1 n)"); 557 | broken("First-child", ":first-child(n)"); 558 | broken("Last-child", ":last-child(n)"); 559 | broken("Only-child", ":only-child(n)"); 560 | broken("Nth-last-last-child", ":nth-last-last-child(1)"); 561 | broken("First-last-child", ":first-last-child"); 562 | broken("Last-last-child", ":last-last-child"); 563 | broken("Only-last-child", ":only-last-child"); 564 | 565 | // Make sure attribute value quoting works correctly. See: #6093 566 | var a = $("").appendTo("#qunit-fixture"); 567 | 568 | broken("Attribute not escaped", "input[name=foo.baz]"); 569 | broken("Attribute not escaped", "input[name=foo[baz]]"); 570 | it('', function () { 571 | a.remove(); 572 | }); 573 | }); 574 | 575 | describe("id", function () { 576 | t("ID Selector", "#body", ["body"]); 577 | t("ID Selector w/ Element", "body#body", ["body"]); 578 | t("ID Selector w/ Element", "ul#first", []); 579 | t("ID selector with existing ID descendant", "#firstp #simon1", ["simon1"]); 580 | t("ID selector with non-existant descendant", "#firstp #foobar", []); 581 | t("ID selector using UTF8", "#台北Táiběi", ["台北Táiběi"]); 582 | t("Multiple ID selectors using UTF8", "#台北Táiběi, #台北", ["台北Táiběi", "台北"]); 583 | t("Descendant ID selector using UTF8", "div #台北", ["台北"]); 584 | t("Child ID selector using UTF8", "form > #台北", ["台北"]); 585 | 586 | t("Escaped ID", "#foo\\:bar", ["foo:bar"]); 587 | t("Escaped ID", "#test\\.foo\\[5\\]bar", ["test.foo[5]bar"]); 588 | t("Descendant escaped ID", "div #foo\\:bar", ["foo:bar"]); 589 | t("Descendant escaped ID", "div #test\\.foo\\[5\\]bar", ["test.foo[5]bar"]); 590 | t("Child escaped ID", "form > #foo\\:bar", ["foo:bar"]); 591 | t("Child escaped ID", "form > #test\\.foo\\[5\\]bar", ["test.foo[5]bar"]); 592 | 593 | it("Escaped ID as context", function () { 594 | var tmp = $("
    ").appendTo("#qunit-fixture"); 595 | expect(select("#fiddle\\\\Foo > span")).toEqual(q(["fiddleSpan"])); 596 | tmp.remove(); 597 | }); 598 | 599 | t("ID Selector, child ID present", "#form > #radio1", ["radio1"]); // bug #267 600 | t("ID Selector, not an ancestor ID", "#form #first", []); 601 | t("ID Selector, not a child ID", "#form > #option1a", []); 602 | 603 | t("All Children of ID", "#foo > *", ["sndp", "en", "sap"]); 604 | t("All Children of ID with no children", "#firstUL > *", []); 605 | 606 | var tmpNode = $("
    " + 607 | "tName1 A" + 608 | "tName2 A" + 609 | "
    tName1 DIV
    " + 610 | "
    ").appendTo("#qunit-fixture"); 611 | 612 | equal(select("#tName1")[0].id, "tName1", "ID selector with same value for a name attribute"); 613 | equal(select("#tName2"), [], "ID selector non-existing but name attribute on an A tag"); 614 | equal(select("#tName2 code"), [], "Leading ID selector non-existing but name attribute on an A tag"); 615 | it('', function () { 616 | tmpNode.remove(); 617 | }); 618 | 619 | it('', function () { 620 | tmpNode = $("").appendTo("#qunit-fixture"); 621 | }); 622 | 623 | t("ID Selector contains backslash", "#backslash\\\\foo", ["backslash\\foo"]); 624 | 625 | t("ID Selector on Form with an input that has a name of 'id'", "#lengthtest", ["lengthtest"]); 626 | 627 | t("ID selector with non-existant ancestor", "#asdfasdf #foobar", []); // bug #986 628 | 629 | equal(select("div#form", document.body), [], "ID selector within the context of another element"); 630 | 631 | t("Underscore ID", "#types_all", ["types_all"]); 632 | t("Dash ID", "#qunit-fixture", ["qunit-fixture"]); 633 | 634 | t("ID with weird characters in it", "#name\\+value", ["name+value"]); 635 | it('', function () { 636 | tmpNode.remove(); 637 | }); 638 | }); 639 | 640 | describe("class", function () { 641 | 642 | t("Class Selector", ".blog", ["mark", "simon"]); 643 | t("Class Selector", ".GROUPS", ["groups"]); 644 | t("Class Selector", ".blog.link", ["simon"]); 645 | t("Class Selector w/ Element", "a.blog", ["mark", "simon"]); 646 | t("Parent Class Selector", "p .blog", ["mark", "simon"]); 647 | 648 | t("Class selector using UTF8", ".台北Táiběi", ["utf8class1"]); 649 | //t( "Class selector using UTF8", ".台北", ["utf8class1","utf8class2"] ); 650 | t("Class selector using UTF8", ".台北Táiběi.台北", ["utf8class1"]); 651 | t("Class selector using UTF8", ".台北Táiběi, .台北", ["utf8class1", "utf8class2"]); 652 | t("Descendant class selector using UTF8", "div .台北Táiběi", ["utf8class1"]); 653 | t("Child class selector using UTF8", "form > .台北Táiběi", ["utf8class1"]); 654 | 655 | t("Escaped Class", ".foo\\:bar", ["foo:bar"]); 656 | t("Escaped Class", ".test\\.foo\\[5\\]bar", ["test.foo[5]bar"]); 657 | t("Descendant escaped Class", "div .foo\\:bar", ["foo:bar"]); 658 | t("Descendant escaped Class", "div .test\\.foo\\[5\\]bar", ["test.foo[5]bar"]); 659 | t("Child escaped Class", "form > .foo\\:bar", ["foo:bar"]); 660 | t("Child escaped Class", "form > .test\\.foo\\[5\\]bar", ["test.foo[5]bar"]); 661 | 662 | 663 | it("Finding a second class.", function () { 664 | var div = document.createElement("div"); 665 | div.innerHTML = "
    "; 666 | expect(select(".e", div)).toEqual([div.firstChild]); 667 | 668 | }); 669 | 670 | it("Finding a modified class.", function () { 671 | var div = document.createElement("div"); 672 | div.innerHTML = "
    "; 673 | div.lastChild.className = "e"; 674 | expect(select(".e", div)).toEqual([div.firstChild, div.lastChild]); 675 | }); 676 | it('".null does not match an element with no class"', function () { 677 | var div = document.createElement("div"); 678 | div.innerHTML = "
    "; 679 | expect(matches('.null', [div]).length).toEqual(0); 680 | div.className = "null"; 681 | expect(matches('.null', [div]).length).toEqual(1); 682 | }); 683 | 684 | it('".null does not match an element with no class"', function () { 685 | var div = document.createElement("div"); 686 | div.innerHTML = "
    "; 687 | expect(matches('.null div', [div.firstChild]).length).toEqual(0); 688 | div.className = "null"; 689 | expect(matches('.null div', [div.firstChild]).length).toEqual(1); 690 | }); 691 | 692 | 693 | it("Classes match Object.prototype properties", function () { 694 | var div = document.createElement("div"); 695 | div.innerHTML = "
    "; 696 | 697 | div.lastChild.className += " hasOwnProperty toString"; 698 | expect(select(".hasOwnProperty.toString", div)).toEqual([div.lastChild]); 699 | }); 700 | }); 701 | 702 | describe("name", function () { 703 | 704 | t("Name selector", "input[name=action]", ["text1"]); 705 | t("Name selector with single quotes", "input[name='action']", ["text1"]); 706 | t("Name selector with double quotes", "input[name=\"action\"]", ["text1"]); 707 | 708 | t("Name selector non-input", "[name=example]", ["name-is-example"]); 709 | t("Name selector non-input", "[name=div]", ["name-is-div"]); 710 | t("Name selector non-input", "*[name=iframe]", ["iframe"]); 711 | 712 | t("Name selector for grouped input", "input[name='types[]']", ["types_all", "types_anime", "types_movie"]); 713 | 714 | 715 | it("Name selector within the context of another element", function () { 716 | var form = document.getElementById("form"); 717 | expect(select("input[name=action]", form)).toEqual(q("text1")); 718 | }); 719 | 720 | it("Name selector for grouped form element within the context of another element", function () { 721 | var form = document.getElementById("form"); 722 | expect(select("input[name='foo[bar]']", form)).toEqual(q("hidden2")); 723 | }); 724 | 725 | it("Make sure that rooted queries on forms (with possible expandos) work.", function () { 726 | var form = $("
    ").appendTo("#fixture"); 727 | expect(select("input", form[0]).length).toEqual(1); 728 | }); 729 | 730 | describe('name nested', function () { 731 | var a; 732 | beforeEach(function () { 733 | a = $("
    tName1 AtName2 A
    tName1 Div
    ") 734 | .appendTo("#qunit-fixture").children(); 735 | }); 736 | 737 | afterEach(function () { 738 | a.parent().remove(); 739 | }); 740 | 741 | equal(function () { 742 | return a.length; 743 | }, 3, "Make sure the right number of elements were inserted."); 744 | equal(function () { 745 | return a[1].id; 746 | }, "tName2ID", "Make sure the right number of elements were inserted."); 747 | 748 | equal(function () { 749 | return select("[name=tName1]")[0]; 750 | }, function () { 751 | return a[0]; 752 | }, "Find elements that have similar IDs"); 753 | equal(function () { 754 | return select("[name=tName2]")[0]; 755 | }, function () { 756 | return a[1]; 757 | }, "Find elements that have similar IDs"); 758 | t("Find elements that have similar IDs", "#tName2ID", ["tName2ID"]); 759 | 760 | }); 761 | 762 | 763 | }); 764 | 765 | describe("multiple", function () { 766 | t("Comma Support1", "#fixture h2, #qunit-fixture p", ["qunit-banner", "qunit-userAgent", "firstp", "ap", "sndp", "en", "sap", "first"]); 767 | t("Comma Support2", "#fixture h2 , #qunit-fixture p", ["qunit-banner", "qunit-userAgent", "firstp", "ap", "sndp", "en", "sap", "first"]); 768 | t("Comma Support3", "#fixture h2 , #qunit-fixture p", ["qunit-banner", "qunit-userAgent", "firstp", "ap", "sndp", "en", "sap", "first"]); 769 | t("Comma Support4", "#fixture h2,#qunit-fixture p", ["qunit-banner", "qunit-userAgent", "firstp", "ap", "sndp", "en", "sap", "first"]); 770 | t("Comma Support5", "#fixture h2,#qunit-fixture p", ["qunit-banner", "qunit-userAgent", "firstp", "ap", "sndp", "en", "sap", "first"]); 771 | t("Comma Support6", "#fixture h2\t,\r#qunit-fixture p", ["qunit-banner", "qunit-userAgent", "firstp", "ap", "sndp", "en", "sap", "first"]); 772 | }); 773 | 774 | describe("child and adjacent", function () { 775 | 776 | t("Child", "p > a", ["simon1", "google", "groups", "mark", "yahoo", "simon"]); 777 | t("Child", "p> a", ["simon1", "google", "groups", "mark", "yahoo", "simon"]); 778 | t("Child", "p >a", ["simon1", "google", "groups", "mark", "yahoo", "simon"]); 779 | t("Child", "p>a", ["simon1", "google", "groups", "mark", "yahoo", "simon"]); 780 | t("Child w/ Class", "p > a.blog", ["mark", "simon"]); 781 | t("All Children", "code > *", ["anchor1", "anchor2"]); 782 | t("All Grandchildren", "p > * > *", ["anchor1", "anchor2"]); 783 | t("Adjacent1", "#qunit-fixture a + a", ["groups"]); 784 | t("Adjacent2", "#qunit-fixture a +a", ["groups"]); 785 | t("Adjacent3", "#qunit-fixture a+ a", ["groups"]); 786 | t("Adjacent4", "#qunit-fixture a+a", ["groups"]); 787 | t("Adjacent5", "p + p", ["ap", "en", "sap"]); 788 | t("Adjacent6", "p#firstp + p", ["ap"]); 789 | t("Adjacent7", "p[lang=en] + p", ["sap"]); 790 | t("Adjacent8", "a.GROUPS + code + a", ["mark"]); 791 | t("Comma, Child, and Adjacent", "#qunit-fixture a + a, code > a", ["groups", "anchor1", "anchor2"]); 792 | t("Element Preceded By1", "#qunit-fixture p ~ div", 793 | ["foo", 'nothiddendiv', "moretests", "tabindex-tests", "liveHandlerOrder", "siblingTest"]); 794 | t("Element Preceded By2", "#first ~ div", ["moretests", "tabindex-tests", "liveHandlerOrder", "siblingTest"]); 795 | t("Element Preceded By3", "#groups ~ a", ["mark"]); 796 | t("Element Preceded By4", "#length ~ input", ["idTest"]); 797 | t("Element Preceded By5", "#siblingfirst ~ em", ["siblingnext", "siblingthird"]); 798 | t("Element Preceded By6 (multiple)", "#siblingTest em ~ em ~ em ~ span", ["siblingspan"]); 799 | 800 | 801 | equal(select("#siblingfirst ~ em"), q("siblingnext", "siblingthird"), "Element Preceded By with a context."); 802 | equal(select("#siblingfirst + em"), q("siblingnext"), "Element Directly Preceded By with a context."); 803 | 804 | 805 | equal(select("#en + p,#en a"), q("yahoo", "sap"), 806 | "Compound selector with context, beginning with sibling test."); 807 | equal(select("#en a, #en + p"), q("yahoo", "sap"), 808 | "Compound selector with context, containing sibling test."); 809 | 810 | t("Multiple combinators selects all levels", "#siblingTest em *", ["siblingchild", "siblinggrandchild", "siblinggreatgrandchild"]); 811 | t("Multiple combinators selects all levels", "#siblingTest > em *", ["siblingchild", "siblinggrandchild", "siblinggreatgrandchild"]); 812 | t("Multiple sibling combinators doesn't miss general siblings", "#siblingTest > em:first-child + em ~ span", ["siblingspan"]); 813 | 814 | equal(select("#listWithTabIndex").length, 1, "Parent div for next test is found via ID "); 815 | equal(select("#__sizzle__").length, 0, "Make sure the temporary id assigned is cleared out"); 816 | equal(select("#listWithTabIndex").length, 1, "Parent div for previous test is still found via ID"); 817 | 818 | t("Verify deep class selector", "div.blah > p > a", []); 819 | 820 | t("No element deep selector", "div.foo > span > a", []); 821 | 822 | t("Non-existant ancestors", ".fototab > .thumbnails > a", []); 823 | }); 824 | 825 | describe("attributes", function () { 826 | 827 | var opt, input, div; 828 | 829 | t("Attribute Exists1", "#qunit-fixture a[title]", ["google"]); 830 | t("Attribute Exists2 (case-insensitive)", "#qunit-fixture a[TITLE]", ["google"]); 831 | t("Attribute Exists3", "#qunit-fixture *[title]", ["google", "text1"]); 832 | t("Attribute Exists4", "#qunit-fixture [title]", ["google", "text1"]); 833 | t("Attribute Exists5", "#qunit-fixture a[ title ]", ["google"]); 834 | 835 | if (!ieVersion || ieVersion > 8) { 836 | // TODO ie67 837 | t("Boolean attribute exists0", "#option2d[selected]", ["option2d"]); 838 | t("Boolean attribute exists1", "#select2 option[selected]", ["option2d"]); 839 | t("Boolean attribute equals2", "#select2 option[selected='selected']", ["option2d"]); 840 | } 841 | 842 | t("Attribute Equals1", "#qunit-fixture a[rel='bookmark']", ["simon1"]); 843 | t("Attribute Equals2", "#qunit-fixture a[rel='bookmark']", ["simon1"]); 844 | t("Attribute Equals3", "#qunit-fixture a[rel=bookmark]", ["simon1"]); 845 | t("Attribute Equals4", "#qunit-fixture a[href='//www.google.com/']", ["google"]); 846 | t("Attribute Equals5", "#qunit-fixture a[ rel = 'bookmark' ]", ["simon1"]); 847 | t("Attribute Equals6 Number", "#qunit-fixture option[value=1]", 848 | ["option1b", "option2b", "option3b", "option4b", "option5c"]); 849 | t("Attribute Equals8 Number", "#foodWithNegativeTabIndex[tabIndex='-1']", ["foodWithNegativeTabIndex"]); 850 | t("Attribute Equals7 Number", "#qunit-fixture li[tabIndex='-1']", ["foodWithNegativeTabIndex"]); 851 | 852 | document.getElementById("anchor2").href = "#2"; 853 | t("href Attribute", "p a[href^='#']", ["anchor2"]); 854 | t("href Attribute", "p a[href*='#']", ["simon1", "anchor2"]); 855 | 856 | t("for Attribute", "form label[for]", ["label-for"]); 857 | t("for Attribute in form", "#form [for=text1]", ["label-for"]); 858 | 859 | t("Attribute containing []1", "input[name^='foo[']", ["hidden2"]); 860 | t("Attribute containing []2", "input[name^='foo[bar]']", ["hidden2"]); 861 | t("Attribute containing []3", "input[name*='[bar]']", ["hidden2"]); 862 | t("Attribute containing []4", "input[name$='bar]']", ["hidden2"]); 863 | t("Attribute containing []5", "input[name$='[bar]']", ["hidden2"]); 864 | t("Attribute containing []6", "input[name$='foo[bar]']", ["hidden2"]); 865 | t("Attribute containing []7", "input[name*='foo[bar]']", ["hidden2"]); 866 | 867 | equal(select("input[data-comma='0,1']"), [document.getElementById("el12087")], "Without context, single-quoted attribute containing ','"); 868 | equal(select("input[data-comma=\"0,1\"]"), [document.getElementById("el12087")], "Without context, double-quoted attribute containing ','"); 869 | equal(select("input[data-comma='0,1']", document.getElementById("t12087")), [document.getElementById("el12087")], "With context, single-quoted attribute containing ','"); 870 | equal(select("input[data-comma=\"0,1\"]", document.getElementById("t12087")), [document.getElementById("el12087")], "With context, double-quoted attribute containing ','"); 871 | 872 | t("Multiple Attribute Equals", "#form input[type='radio'], #form input[type='hidden']", ["radio1", "radio2", "hidden1"]); 873 | t("Multiple Attribute Equals", "#form input[type='radio'], #form input[type=\"hidden\"]", ["radio1", "radio2", "hidden1"]); 874 | t("Multiple Attribute Equals", "#form input[type='radio'], #form input[type=hidden]", ["radio1", "radio2", "hidden1"]); 875 | 876 | t("Attribute selector using UTF8", "span[lang=中文]", ["台北"]); 877 | 878 | t("Attribute Begins With", "a[href ^= '//www']", ["google", "yahoo"]); 879 | t("Attribute Ends With", "a[href $= 'org/']", ["mark"]); 880 | t("Attribute Contains", "a[href *= 'google']", ["google", "groups"]); 881 | 882 | opt = document.getElementById("option1a"); 883 | opt.setAttribute("test", ""); 884 | 885 | ok(matchesSelector(opt, "[id*=option1]"), "Attribute With No Quotes Contains Matches"); 886 | ok(matchesSelector(opt, "[test]"), "Attribute With No Quotes No Content Matches"); 887 | ok(!matchesSelector(opt, "[test^='']"), 888 | "Attribute with empty string value does not match startsWith selector (^=)"); 889 | ok(matchesSelector(opt, "[id=option1a]"), "Attribute With No Quotes Equals Matches"); 890 | ok(matchesSelector(document.getElementById("simon1"), "a[href*='#']"), "Attribute With No Quotes Href Contains Matches"); 891 | 892 | t("Empty values", "#select1 option[value='']", ["option1a"]); 893 | 894 | t("Grouped Form Elements", "input[name='foo[bar]']", ["hidden2"]); 895 | 896 | input = document.getElementById("text1"); 897 | input.title = "Don't click me"; 898 | 899 | ok(matchesSelector(input, "input[title=\"Don't click me\"]"), "Quote within attribute value does not mess up tokenizer"); 900 | 901 | // Make sure attribute value quoting works correctly. See $ #6093; #6428 902 | var tmp; 903 | 904 | it('', function () { 905 | tmp = $( 906 | "" + 907 | "" + 908 | "" + 909 | "" + 910 | "" + 911 | "" + 912 | "" + 913 | "" 914 | ).appendTo("#qunit-fixture"); 915 | }); 916 | 917 | t("Underscores don't need escaping", "input[id=types_all]", ["types_all"]); 918 | 919 | t("Escaped dot", "input[name=foo\\.baz]", ["attrbad_dot"]); 920 | t("Escaped brackets", "input[name=foo\\[baz\\]]", ["attrbad_brackets"]); 921 | t("Escaped quote + right bracket", "input[data-attr='foo_baz\\']']", ["attrbad_injection"]); 922 | 923 | t("Quoted quote", "input[data-attr='\\'']", ["attrbad_quote"]); 924 | 925 | t("Quoted backslash", "input[data-attr='\\\\']", ["attrbad_backslash"]); 926 | t("Quoted backslash quote", "input[data-attr='\\\\\\'']", ["attrbad_backslash_quote"]); 927 | t("Quoted backslash backslash", "input[data-attr='\\\\\\\\']", ["attrbad_backslash_backslash"]); 928 | 929 | t("Quoted backslash backslash (numeric escape)", "input[data-attr='\\5C\\\\']", ["attrbad_backslash_backslash"]); 930 | t("Quoted backslash backslash (numeric escape with trailing space)", "input[data-attr='\\5C \\\\']", ["attrbad_backslash_backslash"]); 931 | t("Quoted backslash backslash (numeric escape with trailing tab)", "input[data-attr='\\5C\t\\\\']", ["attrbad_backslash_backslash"]); 932 | t("Long numeric escape (BMP)", "input[data-attr='\\04e00']", ["attrbad_unicode"]); 933 | 934 | it('', function () { 935 | document.getElementById("attrbad_unicode").setAttribute("data-attr", "\uD834\uDF06A"); 936 | }); 937 | 938 | 939 | // It was too much code to fix Safari 5.x Supplemental Plane crashes (see ba5f09fa404379a87370ec905ffa47f8ac40aaa3) 940 | // t( "Long numeric escape (non-BMP)", "input[data-attr='\\01D306A']", ["attrbad_unicode"] ); 941 | 942 | if (!ieVersion || ieVersion > 8) { 943 | if (!ieVersion || ieVersion > 9) { 944 | t("input[type=search]0", "#search[type=search]", ["search"]); 945 | t("input[type=text]", "#form input[type=text]", ["text1", "text2", "hidden2", "name"]); 946 | t("input[type=search]", "#form input[type=search]", ["search"]); 947 | } else { 948 | t("input[type=text]", "#form input[type=text]", ["text1", "text2", "hidden2", "name", "search"]); 949 | } 950 | } 951 | 952 | // #3279 953 | div = document.createElement("div"); 954 | div.innerHTML = "
    "; 955 | 956 | equal(select("[xml\\:test]", div), [div.firstChild], "Finding by attribute with escaped characters."); 957 | 958 | it('', function () { 959 | tmp.remove(); 960 | }); 961 | 962 | }); 963 | 964 | describe("pseudo - (parent|empty)", function () { 965 | t("Empty", "#fixture ul:empty", ["firstUL"]); 966 | t("Empty with comment node", "ol:empty", ["empty"]); 967 | t("Not Empty with comment node and extra node", "b:empty", []); 968 | }); 969 | 970 | describe("pseudo - (first|last|only)-(child|of-type)", function () { 971 | 972 | t("First Child", "p:first-child", ["firstp", "sndp"]); 973 | t("First Child (leading id)", "#qunit-fixture p:first-child", ["firstp", "sndp"]); 974 | t("First Child (leading class)", ".nothiddendiv div:first-child", ["nothiddendivchild"]); 975 | t("First Child (case-insensitive)", "#qunit-fixture p:FIRST-CHILD", ["firstp", "sndp"]); 976 | 977 | t("Last Child", "p:last-child", ["sap"]); 978 | t("Last Child (leading id)", "#qunit-fixture a:last-child", 979 | ["simon1", "anchor1", "mark", "yahoo", "anchor2", "simon", "liveLink1", "liveLink2"]); 980 | 981 | t("Only Child", "#qunit-fixture a:only-child", ["simon1", "anchor1", "yahoo", "anchor2", "liveLink1", "liveLink2"]); 982 | 983 | t("First-of-type", "#qunit-fixture > p:first-of-type", ["firstp"]); 984 | t("Last-of-type", "#qunit-fixture > p:last-of-type", ["first"]); 985 | t("Only-of-type", "#qunit-fixture > :only-of-type", ["name+value", "firstUL", "empty", "floatTest", "iframe", "table"]); 986 | 987 | it("No longer second child", function () { 988 | // Verify that the child position isn't being cached improperly 989 | var secondChildren = $("p:nth-child(2)").before("
    "); 990 | expect(select("p:nth-child(2)")).toEqual([]); 991 | secondChildren.prev().remove(); 992 | }); 993 | 994 | t("Restored second child", "p:nth-child(2)", ["ap", "en"]); 995 | }); 996 | 997 | describe("pseudo - nth-child", function () { 998 | 999 | t("Nth-child", "p:nth-child(1)", ["firstp", "sndp"]); 1000 | t("Nth-child (with whitespace)", "p:nth-child( 1 )", ["firstp", "sndp"]); 1001 | t("Not nth-child", "#qunit-fixture p:not(:nth-child(1))", ["ap", "en", "sap", "first"]); 1002 | 1003 | t("Nth-child(2)", "#qunit-fixture form#form > *:nth-child(2)", ["text1"]); 1004 | t("Nth-child(2)", "#qunit-fixture form#form > :nth-child(2)", ["text1"]); 1005 | 1006 | equal(select(":nth-child(n)", null, [document.createElement("a")].concat(q("ap"))), q("ap"), "Seeded nth-child"); 1007 | }); 1008 | 1009 | describe("pseudo - nth-last-child", function () { 1010 | 1011 | t("Nth-last-child", "form:nth-last-child(5)", ["testForm"]); 1012 | t("Nth-last-child (with whitespace)", "form:nth-last-child( 5 )", ["testForm"]); 1013 | t("Not nth-last-child", "#qunit-fixture p:not(:nth-last-child(1))", ["firstp", "ap", "sndp", "en", "first"]); 1014 | 1015 | equal(select(":nth-last-child(n)", null, [document.createElement("a")].concat(q("ap"))), q("ap"), 1016 | "Seeded nth-last-child"); 1017 | }); 1018 | 1019 | describe("pseudo - nth-of-type", function () { 1020 | t("Nth-of-type(-1)", ":nth-of-type(-1)", []); 1021 | t("Nth-of-type(3)", "#ap :nth-of-type(3)", ["mark"]); 1022 | t("Nth-of-type(n)", "#ap :nth-of-type(n)", ["google", "groups", "code1", "anchor1", "mark"]); 1023 | t("Nth-of-type(0n+3)", "#ap :nth-of-type(0n+3)", ["mark"]); 1024 | t("Nth-of-type(2n)", "#ap :nth-of-type(2n)", ["groups"]); 1025 | t("Nth-of-type(even)", "#ap :nth-of-type(even)", ["groups"]); 1026 | t("Nth-of-type(2n+1)", "#ap :nth-of-type(2n+1)", ["google", "code1", "anchor1", "mark"]); 1027 | t("Nth-of-type(odd)", "#ap :nth-of-type(odd)", ["google", "code1", "anchor1", "mark"]); 1028 | 1029 | 1030 | it("Nth-of-type(-n+2)", function () { 1031 | expect(select("#qunit-fixture > :nth-of-type(-n+2)", null, select('#qunit-fixture > *'))) 1032 | .toEqual(makeArray($("#qunit-fixture > :nth-of-type(-n+2)"))); 1033 | }); 1034 | 1035 | }); 1036 | 1037 | describe("pseudo - nth-last-of-type", function () { 1038 | t("Nth-last-of-type(-1)", ":nth-last-of-type(-1)", []); 1039 | t("Nth-last-of-type(3)", "#ap :nth-last-of-type(3)", ["google"]); 1040 | t("Nth-last-of-type(n)", "#ap :nth-last-of-type(n)", ["google", "groups", "code1", "anchor1", "mark"]); 1041 | t("Nth-last-of-type(0n+3)", "#ap :nth-last-of-type(0n+3)", ["google"]); 1042 | t("Nth-last-of-type(2n)", "#ap :nth-last-of-type(2n)", ["groups"]); 1043 | t("Nth-last-of-type(even)", "#ap :nth-last-of-type(even)", ["groups"]); 1044 | t("Nth-last-of-type(2n+1)", "#ap :nth-last-of-type(2n+1)", ["google", "code1", "anchor1", "mark"]); 1045 | t("Nth-last-of-type(odd)", "#ap :nth-last-of-type(odd)", ["google", "code1", "anchor1", "mark"]); 1046 | 1047 | t("Nth-last-of-type(-n+2)", 1048 | "#qunit-fixture > :nth-last-of-type(-n+2)", 1049 | ["ap", "name+value", "first", "firstUL", "empty", "floatTest", "iframe", "table", "name-tests", "testForm", "liveHandlerOrder", "siblingTest"]); 1050 | }); 1051 | 1052 | describe("pseudo - misc", function () { 1053 | 1054 | // Recreate tmp 1055 | 1056 | it('div focus', function () { 1057 | var tmp = document.createElement("div"); 1058 | tmp.id = "tmp_input"; 1059 | tmp.innerHTML = "Hello I am focusable."; 1060 | // Setting tabIndex should make the element focusable 1061 | // http://dev.w3.org/html5/spec/single-page.html#focus-management 1062 | document.body.appendChild(tmp); 1063 | tmp.tabIndex = 0; 1064 | tmp.focus(); 1065 | if (document.activeElement !== tmp || (document.hasFocus && !document.hasFocus()) || 1066 | (document.querySelectorAll && !document.querySelectorAll("div:focus").length)) { 1067 | } else { 1068 | expect(select(":focus")).toEqual([tmp]); 1069 | expect(matchesSelector(tmp, ":focus")).toEqual(true); 1070 | } 1071 | 1072 | // Blur tmp 1073 | tmp.blur(); 1074 | document.body.focus(); 1075 | expect(matchesSelector(tmp, ":focus")).toEqual(false); 1076 | document.body.removeChild(tmp); 1077 | }); 1078 | 1079 | 1080 | it('input focus', function () { 1081 | var tmp = document.createElement("input"); 1082 | tmp.type = "text"; 1083 | tmp.id = "tmp_input"; 1084 | 1085 | document.body.appendChild(tmp); 1086 | tmp.focus(); 1087 | 1088 | if (document.activeElement !== tmp || (document.hasFocus && !document.hasFocus()) || 1089 | (document.querySelectorAll && !document.querySelectorAll("div:focus").length)) { 1090 | } else { 1091 | expect(select(":focus")).toEqual(q("tmp_input")); 1092 | expect(matchesSelector(tmp, ":focus")).toEqual(true); 1093 | } 1094 | 1095 | // Blur tmp 1096 | tmp.blur(); 1097 | document.body.focus(); 1098 | expect(matchesSelector(tmp, ":focus")).toEqual(false); 1099 | document.body.removeChild(tmp); 1100 | }); 1101 | 1102 | 1103 | equal( 1104 | select("[id='select1'] *:not(:last-child), [id='select2'] *:not(:last-child)", q("qunit-fixture")[0]), 1105 | q("option1a", "option1b", "option1c", "option2a", "option2b", "option2c"), 1106 | "caching system tolerates recursive selection" 1107 | ); 1108 | 1109 | }); 1110 | 1111 | 1112 | describe("pseudo - :not", function () { 1113 | 1114 | t("Not", "a.blog:not(.link)", ["mark"]); 1115 | 1116 | t(":not() failing interior", "#qunit-fixture p:not(.foo)", ["firstp", "ap", "sndp", "en", "sap", "first"]); 1117 | t(":not() failing interior", "#qunit-fixture p:not(#blargh)", ["firstp", "ap", "sndp", "en", "sap", "first"]); 1118 | t(":not() failing interior", "#qunit-fixture p:not(#blargh)", ["firstp", "ap", "sndp", "en", "sap", "first"]); 1119 | t(":not() failing interior", "#qunit-fixture p:not(#blargh)", ["firstp", "ap", "sndp", "en", "sap", "first"]); 1120 | 1121 | t(":not Multiple", "#qunit-fixture p:not(a)", ["firstp", "ap", "sndp", "en", "sap", "first"]); 1122 | t(":not Multiple", "#qunit-fixture p:not( a )", ["firstp", "ap", "sndp", "en", "sap", "first"]); 1123 | t(":not Multiple", "#qunit-fixture p:not( p )", []); 1124 | t(":not Multiple", "p:not(p)", []); 1125 | t(":not Multiple", "p:not(p)", []); 1126 | t(":not Multiple", "p:not(p)", []); 1127 | t(":not Multiple", "p:not(p)", []); 1128 | 1129 | t("No element not selector", ".container div:not(.excluded) div", []); 1130 | 1131 | t(":not() Existing attribute", "#form select:not([multiple])", ["select1", "select2", "select5"]); 1132 | t(":not() Equals attribute", "#form select:not([name=select1])", ["select2", "select3", "select4", "select5"]); 1133 | t(":not() Equals quoted attribute", "#form select:not([name='select1'])", ["select2", "select3", "select4", "select5"]); 1134 | 1135 | t(":not() Multiple Class", "#foo a:not(.blog)", ["yahoo", "anchor2"]); 1136 | t(":not() Multiple Class", "#foo a:not(.link)", ["yahoo", "anchor2"]); 1137 | }); 1138 | 1139 | describe("pseudo - form", function () { 1140 | 1141 | var extraTexts = $("").appendTo("#form"); 1142 | 1143 | 1144 | t("Selected Option Element are also :checked", "#form option:checked", ["option1a", "option2d", "option3b", "option3c", "option4b", "option4c", "option4d", "option5a"]); 1145 | t("Hidden inputs should be treated as enabled. See QSA test.", "#hidden1:enabled", ["hidden1"]); 1146 | 1147 | it('xx', function () { 1148 | extraTexts.remove(); 1149 | }); 1150 | 1151 | }); 1152 | 1153 | describe("pseudo - :target and :root", function () { 1154 | 1155 | it(':target', function () { 1156 | // Target 1157 | var $link = $("").attr({ 1158 | href: "#", 1159 | id: "new-link" 1160 | }).appendTo("#qunit-fixture"); 1161 | 1162 | var oldHash = window.location.hash; 1163 | window.location.hash = "new-link"; 1164 | 1165 | expect(select(":target")).toEqual([$link[0]]); 1166 | 1167 | $link.remove(); 1168 | window.location.hash = oldHash; 1169 | }); 1170 | 1171 | // Root 1172 | equal(select(":root")[0], document.documentElement, ":root selector"); 1173 | }); 1174 | 1175 | describe("pseudo - :lang", function () { 1176 | 1177 | document.documentElement.lang = 'en-us'; 1178 | 1179 | var docElem = document.documentElement, 1180 | docXmlLang = docElem.getAttribute("xml:lang"), 1181 | docLang = docElem.lang, 1182 | foo = document.getElementById("foo"), 1183 | anchor = document.getElementById("anchor2"), 1184 | xml = createWithFriesXML(), 1185 | testLang = function (text, elem, container, lang, extra) { 1186 | 1187 | it(lang + "-" + extra, function () { 1188 | if (typeof elem === 'function') { 1189 | elem = elem(); 1190 | } 1191 | if (typeof container === 'function') { 1192 | container = container(); 1193 | } 1194 | var message, 1195 | full = lang + "-" + extra; 1196 | 1197 | var isXML = container.ownerDocument.documentElement.nodeName.toUpperCase() !== "HTML"; 1198 | 1199 | // while (cur && cur != container.ownerDocument.documentElement) { 1200 | // cur.lang = ''; 1201 | // cur = cur.parentNode; 1202 | // } 1203 | 1204 | message = "lang=" + lang + " " + text; 1205 | container.setAttribute( 1206 | !isXML ? 1207 | "lang" : "xml:lang", lang); 1208 | 1209 | assertMatch(message, elem, ":lang(" + lang + ")"); 1210 | assertMatch(message, elem, ":lang(" + mixCase(lang) + ")"); 1211 | assertNoMatch(message, elem, ":lang(" + full + ")"); 1212 | assertNoMatch(message, elem, ":lang(" + mixCase(full) + ")"); 1213 | assertNoMatch(message, elem, ":lang(" + lang + "-)"); 1214 | assertNoMatch(message, elem, ":lang(" + full + "-)"); 1215 | assertNoMatch(message, elem, ":lang(" + lang + "glish)"); 1216 | assertNoMatch(message, elem, ":lang(" + full + "glish)"); 1217 | 1218 | message = "lang=" + full + " " + text; 1219 | container.setAttribute( 1220 | !isXML ? 1221 | "lang" : "xml:lang", full); 1222 | 1223 | assertMatch(message, elem, ":lang(" + lang + ")"); 1224 | assertMatch(message, elem, ":lang(" + mixCase(lang) + ")"); 1225 | assertMatch(message, elem, ":lang(" + full + ")"); 1226 | assertMatch(message, elem, ":lang(" + mixCase(full) + ")"); 1227 | assertNoMatch(message, elem, ":lang(" + lang + "-)"); 1228 | assertNoMatch(message, elem, ":lang(" + full + "-)"); 1229 | assertNoMatch(message, elem, ":lang(" + lang + "glish)"); 1230 | assertNoMatch(message, elem, ":lang(" + full + "glish)"); 1231 | }); 1232 | 1233 | }, 1234 | mixCase = function (str) { 1235 | var ret = str.split(""), 1236 | i = ret.length; 1237 | while (i--) { 1238 | if (i & 1) { 1239 | ret[i] = ret[i].toUpperCase(); 1240 | } 1241 | } 1242 | return ret.join(""); 1243 | }, 1244 | assertMatch = function (text, elem, selector) { 1245 | 1246 | var r = matchesSelector(elem, selector); 1247 | if (!r) { 1248 | matchesSelector(elem, selector); 1249 | } 1250 | expect(r).toEqual(true); 1251 | if (!r) { 1252 | console.error('error:' + text + " should match " + selector); 1253 | } 1254 | 1255 | }, 1256 | assertNoMatch = function (text, elem, selector) { 1257 | var r = matchesSelector(elem, selector); 1258 | expect(r).toEqual(false); 1259 | if (r) { 1260 | console.error('error:' + text + " should fail " + selector); 1261 | } 1262 | }; 1263 | 1264 | // Prefixing and inheritance 1265 | ok(function () { 1266 | return matchesSelector(docElem, ":lang(" + docElem.lang + ")"); 1267 | }, "starting :lang"); 1268 | 1269 | testLang("document", function () { 1270 | return anchor; 1271 | }, docElem, "en", "us"); 1272 | 1273 | testLang("grandparent", function () { 1274 | return anchor; 1275 | }, 1276 | function () { 1277 | return anchor.parentNode.parentNode; 1278 | }, "yue", "hk"); 1279 | 1280 | ok(function () { 1281 | return !matchesSelector(anchor, ":lang(en), :lang(en-us)"); 1282 | }, 1283 | ":lang does not look above an ancestor with specified lang"); 1284 | 1285 | testLang("self", function () { 1286 | return anchor; 1287 | }, function () { 1288 | return anchor; 1289 | }, "es", "419"); 1290 | 1291 | ok(function () { 1292 | return !matchesSelector(anchor, ":lang(en), :lang(en-us), :lang(yue), :lang(yue-hk)"); 1293 | }, 1294 | ":lang does not look above self with specified lang"); 1295 | 1296 | // Searching by language tag 1297 | it('', function () { 1298 | anchor.parentNode.parentNode.lang = "arab"; 1299 | anchor.parentNode.lang = anchor.parentNode.id = "ara-sa"; 1300 | anchor.lang = "ara"; 1301 | }); 1302 | 1303 | equal(function () { 1304 | return select(":lang(ara)", foo); 1305 | }, [anchor.parentNode, anchor], "Find by :lang"); 1306 | 1307 | it('', function () { 1308 | // Selector validity 1309 | anchor.parentNode.lang = "ara"; 1310 | anchor.lang = "ara\\b"; 1311 | }); 1312 | 1313 | equal(function () { 1314 | return select(":lang(ara\\b)", foo); 1315 | }, [], ":lang respects backslashes"); 1316 | 1317 | equal(function () { 1318 | return select(":lang(ara\\\\b)", foo); 1319 | }, [anchor], 1320 | ":lang respects escaped backslashes"); 1321 | 1322 | it(":lang value must be a valid identifier", function () { 1323 | try { 1324 | select("dl:lang(c++)"); 1325 | } catch (e) { 1326 | expect(e.message.indexOf("Syntax error")).toBeGreaterThan(-1); 1327 | } 1328 | }); 1329 | 1330 | it('', function () { 1331 | // XML 1332 | foo = $("response", xml)[0]; 1333 | anchor = $("#seite1", xml)[0]; 1334 | 1335 | }); 1336 | 1337 | testLang("XML document", function () { 1338 | return anchor; 1339 | }, xml.documentElement, "en", "us"); 1340 | 1341 | testLang("XML grandparent", function () { 1342 | return anchor; 1343 | }, function () { 1344 | return foo; 1345 | }, "yue", "hk"); 1346 | 1347 | ok(function () { 1348 | return !matchesSelector(anchor, ":lang(en), :lang(en-us)"); 1349 | }, 1350 | "XML :lang does not look above an ancestor with specified lang"); 1351 | 1352 | testLang("XML self", function () { 1353 | return anchor; 1354 | }, function () { 1355 | return anchor; 1356 | }, "es", "419"); 1357 | 1358 | ok(function () { 1359 | return !matchesSelector(anchor, ":lang(en), :lang(en-us), :lang(yue), :lang(yue-hk)"); 1360 | }, 1361 | "XML :lang does not look above self with specified lang"); 1362 | 1363 | it('', function () { 1364 | // Cleanup 1365 | if (docXmlLang === null) { 1366 | docElem.removeAttribute("xml:lang"); 1367 | } else { 1368 | docElem.setAttribute("xml:lang", docXmlLang); 1369 | } 1370 | docElem.lang = docLang; 1371 | }); 1372 | }); 1373 | 1374 | describe("caching", function () { 1375 | select(":not(code)", document.getElementById("ap")); 1376 | equal(select(":not(code)", document.getElementById("foo")), q("sndp", "en", "yahoo", "sap", "anchor2", "simon"), "Reusing selector with new context"); 1377 | }); 1378 | 1379 | describe('clean', function () { 1380 | it('remove all', function () { 1381 | $('#fixture').remove(); 1382 | }); 1383 | }); 1384 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "query-selector", 3 | "version": "2.0.0", 4 | "description": "javascript implementation of querySelectorAll", 5 | "author": "yiminghe ", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "http://github.com/yiminghe/query-selector.git" 10 | }, 11 | "@pika/pack": { 12 | "pipeline": [ 13 | [ 14 | "@pika/plugin-standard-pkg", 15 | { 16 | "exclude": [ 17 | "__tests__/**/*" 18 | ] 19 | } 20 | ], 21 | [ 22 | "pika-plugin-build-web-babel" 23 | ], 24 | [ 25 | "@pika/plugin-build-node" 26 | ] 27 | ] 28 | }, 29 | "scripts": { 30 | "test": "jest", 31 | "coverage": "npm test -- --coverage && cat ./coverage/lcov.info | coveralls", 32 | "prettier": "prettier --write \"{src,stories}/**/*.{js,tsx}\"", 33 | "start": "start-storybook -p 6006", 34 | "pub": "npm run build && npm publish pkg && git push", 35 | "build": "pack build", 36 | "lint-staged": "lint-staged" 37 | }, 38 | "devDependencies": { 39 | "@pika/pack": "^0.5.0", 40 | "@pika/plugin-build-node": "0.6.x", 41 | "@pika/plugin-standard-pkg": "0.6.x", 42 | "@pika/types": "0.6.x", 43 | "coveralls": "^3.0.6", 44 | "jest": "^24.8.0", 45 | "jquery": "^3.4.1", 46 | "lint-staged": "^9.2.1", 47 | "pika-plugin-build-web-babel": "^0.6.0", 48 | "pre-commit": "1.x", 49 | "prettier": "^1.18.2" 50 | }, 51 | "lint-staged": { 52 | "*.{tsx,js,jsx,ts}": [ 53 | "prettier --write", 54 | "git add" 55 | ] 56 | }, 57 | "pre-commit": [ 58 | "lint-staged" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ignore 3 | * css3 selector engine for ie6-8 4 | * @author yiminghe@gmail.com 5 | */ 6 | 7 | import util from './query-selector/util'; 8 | import parser from './query-selector/parser'; 9 | 10 | var EXPANDO_SELECTOR_KEY = '_ks_data_selector_id_', 11 | caches = {}, 12 | isContextXML, 13 | uuid = 0, 14 | subMatchesCache = {}, 15 | getAttr = function (el, name) { 16 | if (isContextXML) { 17 | return util.getSimpleAttr(el, name); 18 | } else { 19 | return util.attr(el, name); 20 | } 21 | }, 22 | hasSingleClass = util.hasSingleClass, 23 | isTag = util.isTag, 24 | aNPlusB = /^(([+-]?(?:\d+)?)?n)?([+-]?\d+)?$/; 25 | 26 | // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters 27 | var unescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, 28 | unescapeFn = function (_, escaped) { 29 | var high = '0x' + escaped - 0x10000; 30 | // NaN means non-codepoint 31 | return isNaN(high) ? 32 | escaped : 33 | // BMP codepoint 34 | high < 0 ? 35 | String.fromCharCode(high + 0x10000) : 36 | // Supplemental Plane codepoint (surrogate pair) 37 | String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00); 38 | }; 39 | 40 | var matchExpr; 41 | 42 | var pseudoFnExpr = { 43 | 'nth-child'(el, param) { 44 | var ab = getAb(param), 45 | a = ab.a, 46 | b = ab.b; 47 | if (a === 0 && b === 0) { 48 | return 0; 49 | } 50 | var index = 0, 51 | parent = el.parentNode; 52 | if (parent) { 53 | var childNodes = parent.childNodes, 54 | count = 0, 55 | child, 56 | ret, 57 | len = childNodes.length; 58 | for (; count < len; count++) { 59 | child = childNodes[count]; 60 | if (child.nodeType === 1) { 61 | index++; 62 | ret = matchIndexByAb(index, a, b, child === el); 63 | if (ret !== undefined) { 64 | return ret; 65 | } 66 | } 67 | } 68 | } 69 | return 0; 70 | }, 71 | 'nth-last-child'(el, param) { 72 | var ab = getAb(param), 73 | a = ab.a, 74 | b = ab.b; 75 | if (a === 0 && b === 0) { 76 | return 0; 77 | } 78 | var index = 0, 79 | parent = el.parentNode; 80 | if (parent) { 81 | var childNodes = parent.childNodes, 82 | len = childNodes.length, 83 | count = len - 1, 84 | child, 85 | ret; 86 | for (; count >= 0; count--) { 87 | child = childNodes[count]; 88 | if (child.nodeType === 1) { 89 | index++; 90 | ret = matchIndexByAb(index, a, b, child === el); 91 | if (ret !== undefined) { 92 | return ret; 93 | } 94 | } 95 | } 96 | } 97 | return 0; 98 | }, 99 | 'nth-of-type'(el, param) { 100 | var ab = getAb(param), 101 | a = ab.a, 102 | b = ab.b; 103 | if (a === 0 && b === 0) { 104 | return 0; 105 | } 106 | var index = 0, 107 | parent = el.parentNode; 108 | if (parent) { 109 | var childNodes = parent.childNodes, 110 | elType = el.tagName, 111 | count = 0, 112 | child, 113 | ret, 114 | len = childNodes.length; 115 | for (; count < len; count++) { 116 | child = childNodes[count]; 117 | if (child.tagName === elType) { 118 | index++; 119 | ret = matchIndexByAb(index, a, b, child === el); 120 | if (ret !== undefined) { 121 | return ret; 122 | } 123 | } 124 | } 125 | } 126 | return 0; 127 | }, 128 | 'nth-last-of-type'(el, param) { 129 | var ab = getAb(param), 130 | a = ab.a, 131 | b = ab.b; 132 | if (a === 0 && b === 0) { 133 | return 0; 134 | } 135 | var index = 0, 136 | parent = el.parentNode; 137 | if (parent) { 138 | var childNodes = parent.childNodes, 139 | len = childNodes.length, 140 | elType = el.tagName, 141 | count = len - 1, 142 | child, 143 | ret; 144 | for (; count >= 0; count--) { 145 | child = childNodes[count]; 146 | if (child.tagName === elType) { 147 | index++; 148 | ret = matchIndexByAb(index, a, b, child === el); 149 | if (ret !== undefined) { 150 | return ret; 151 | } 152 | } 153 | } 154 | } 155 | return 0; 156 | }, 157 | lang(el, lang) { 158 | var elLang; 159 | lang = unEscape(lang.toLowerCase()); 160 | do { 161 | if ((elLang = (isContextXML ? 162 | el.getAttribute('xml:lang') || el.getAttribute('lang') : 163 | el.lang))) { 164 | elLang = elLang.toLowerCase(); 165 | return elLang === lang || elLang.indexOf(lang + '-') === 0; 166 | } 167 | } while ((el = el.parentNode) && el.nodeType === 1); 168 | return false; 169 | }, 170 | not(el, negationArg) { 171 | return !matchExpr[negationArg.t](el, negationArg.value); 172 | } 173 | }; 174 | 175 | var pseudoIdentExpr = { 176 | empty(el) { 177 | var childNodes = el.childNodes, 178 | index = 0, 179 | len = childNodes.length, 180 | child, 181 | nodeType; 182 | for (; index < len; index++) { 183 | child = childNodes[index]; 184 | nodeType = child.nodeType; 185 | // only element nodes and content nodes 186 | // (such as Dom [Dom-LEVEL-3-CORE] text nodes, 187 | // CDATA nodes, and entity references 188 | if (nodeType === 1 || nodeType === 3 || nodeType === 4 || nodeType === 5) { 189 | return 0; 190 | } 191 | } 192 | return 1; 193 | }, 194 | root(el) { 195 | if (el.nodeType === 9) { 196 | return true; 197 | } 198 | return el.ownerDocument && 199 | el === el.ownerDocument.documentElement; 200 | }, 201 | 'first-child'(el) { 202 | return pseudoFnExpr['nth-child'](el, 1); 203 | }, 204 | 'last-child'(el) { 205 | return pseudoFnExpr['nth-last-child'](el, 1); 206 | }, 207 | 'first-of-type'(el) { 208 | return pseudoFnExpr['nth-of-type'](el, 1); 209 | }, 210 | 'last-of-type'(el) { 211 | return pseudoFnExpr['nth-last-of-type'](el, 1); 212 | }, 213 | 'only-child'(el) { 214 | return pseudoIdentExpr['first-child'](el) && 215 | pseudoIdentExpr['last-child'](el); 216 | }, 217 | 'only-of-type'(el) { 218 | return pseudoIdentExpr['first-of-type'](el) && 219 | pseudoIdentExpr['last-of-type'](el); 220 | }, 221 | focus(el) { 222 | var doc = el.ownerDocument; 223 | return doc && el === doc.activeElement && 224 | (!doc.hasFocus || doc.hasFocus()) && !!(el.type || el.href || el.tabIndex >= 0); 225 | }, 226 | target(el) { 227 | var hash = location.hash; 228 | return hash && hash.slice(1) === getAttr(el, 'id'); 229 | }, 230 | enabled(el) { 231 | return !el.disabled; 232 | }, 233 | disabled(el) { 234 | return el.disabled; 235 | }, 236 | checked(el) { 237 | var nodeName = el.nodeName.toLowerCase(); 238 | return (nodeName === 'input' && el.checked) || 239 | (nodeName === 'option' && el.selected); 240 | } 241 | }; 242 | 243 | var attributeExpr = { 244 | '~='(elValue, value) { 245 | if (!value || value.indexOf(' ') > -1) { 246 | return 0; 247 | } 248 | return (' ' + elValue + ' ').indexOf(' ' + value + ' ') !== -1; 249 | }, 250 | '|='(elValue, value) { 251 | return (' ' + elValue).indexOf(' ' + value + '-') !== -1; 252 | }, 253 | '^='(elValue, value) { 254 | return value && util.startsWith(elValue, value); 255 | }, 256 | '$='(elValue, value) { 257 | return value && util.endsWith(elValue, value); 258 | }, 259 | '*='(elValue, value) { 260 | return value && elValue.indexOf(value) !== -1; 261 | }, 262 | '='(elValue, value) { 263 | return elValue === value; 264 | } 265 | }; 266 | 267 | var relativeExpr = { 268 | '>': { 269 | dir: 'parentNode', 270 | immediate: 1 271 | }, 272 | ' ': { 273 | dir: 'parentNode' 274 | }, 275 | '+': { 276 | dir: 'previousSibling', 277 | immediate: 1 278 | }, 279 | '~': { 280 | dir: 'previousSibling' 281 | } 282 | }; 283 | 284 | matchExpr = { 285 | tag: isTag, 286 | cls: hasSingleClass, 287 | id(el, value) { 288 | return getAttr(el, 'id') === value; 289 | }, 290 | attrib(el, value) { 291 | var name = value.ident; 292 | if (!isContextXML) { 293 | name = name.toLowerCase(); 294 | } 295 | var elValue = getAttr(el, name); 296 | var match = value.match; 297 | if (!match && elValue !== undefined) { 298 | return 1; 299 | } else if (match) { 300 | if (elValue === undefined) { 301 | return 0; 302 | } 303 | var matchFn = attributeExpr[match]; 304 | if (matchFn) { 305 | return matchFn(elValue + '', value.value + ''); 306 | } 307 | } 308 | return 0; 309 | }, 310 | pseudo(el, value) { 311 | var fn, fnStr, ident; 312 | if ((fnStr = value.fn)) { 313 | if (!(fn = pseudoFnExpr[fnStr])) { 314 | throw new SyntaxError('Syntax error: not support pseudo: ' + fnStr); 315 | } 316 | return fn(el, value.param); 317 | } 318 | if ((ident = value.ident)) { 319 | if (!pseudoIdentExpr[ident]) { 320 | throw new SyntaxError('Syntax error: not support pseudo: ' + ident); 321 | } 322 | return pseudoIdentExpr[ident](el); 323 | } 324 | return 0; 325 | } 326 | }; 327 | 328 | function unEscape(str) { 329 | return str.replace(unescape, unescapeFn); 330 | } 331 | 332 | parser.lexer.yy = { 333 | trim: util.trim, 334 | unEscape: unEscape, 335 | unEscapeStr(str) { 336 | return this.unEscape(str.slice(1, -1)); 337 | } 338 | }; 339 | 340 | function resetStatus() { 341 | subMatchesCache = {}; 342 | } 343 | 344 | function dir(el, direction) { 345 | do { 346 | el = el[direction]; 347 | } while (el && el.nodeType !== 1); 348 | return el; 349 | } 350 | 351 | function getAb(param) { 352 | var a = 0, 353 | match, 354 | b = 0; 355 | if (typeof param === 'number') { 356 | b = param; 357 | } else if (param === 'odd') { 358 | a = 2; 359 | b = 1; 360 | } else if (param === 'even') { 361 | a = 2; 362 | b = 0; 363 | } else if ((match = param.replace(/\s/g, '').match(aNPlusB))) { 364 | if (match[1]) { 365 | a = parseInt(match[2], 10); 366 | if (isNaN(a)) { 367 | if (match[2] === '-') { 368 | a = -1; 369 | } else { 370 | a = 1; 371 | } 372 | } 373 | } else { 374 | a = 0; 375 | } 376 | b = parseInt(match[3], 10) || 0; 377 | } 378 | return { 379 | a: a, 380 | b: b 381 | }; 382 | } 383 | 384 | function matchIndexByAb(index, a, b, eq) { 385 | if (a === 0) { 386 | if (index === b) { 387 | return eq; 388 | } 389 | } else { 390 | if ((index - b) / a >= 0 && (index - b) % a === 0 && eq) { 391 | return 1; 392 | } 393 | } 394 | return undefined; 395 | } 396 | 397 | function isXML(elem) { 398 | var documentElement = elem && (elem.ownerDocument || elem).documentElement; 399 | return documentElement ? documentElement.nodeName.toLowerCase() !== 'html' : false; 400 | } 401 | 402 | function matches(str, seeds) { 403 | return select(str, null, seeds); 404 | } 405 | 406 | function singleMatch(el, match) { 407 | if (!match) { 408 | return true; 409 | } 410 | if (!el) { 411 | return false; 412 | } 413 | 414 | if (el.nodeType === 9) { 415 | return false; 416 | } 417 | 418 | var matched = 1, 419 | matchSuffix = match.suffix, 420 | matchSuffixLen, 421 | matchSuffixIndex; 422 | 423 | if (match.t === 'tag') { 424 | matched &= matchExpr.tag(el, match.value); 425 | } 426 | 427 | if (matched && matchSuffix) { 428 | matchSuffixLen = matchSuffix.length; 429 | matchSuffixIndex = 0; 430 | for (; matched && matchSuffixIndex < matchSuffixLen; matchSuffixIndex++) { 431 | var singleMatchSuffix = matchSuffix[matchSuffixIndex], 432 | singleMatchSuffixType = singleMatchSuffix.t; 433 | if (matchExpr[singleMatchSuffixType]) { 434 | matched &= matchExpr[singleMatchSuffixType](el, singleMatchSuffix.value); 435 | } 436 | } 437 | } 438 | 439 | return matched; 440 | } 441 | 442 | // match by adjacent immediate single selector match 443 | function matchImmediate(el, match) { 444 | var matched = 1, 445 | startEl = el, 446 | relativeOp, 447 | startMatch = match; 448 | 449 | do { 450 | matched &= singleMatch(el, match); 451 | if (matched) { 452 | // advance 453 | match = match && match.prev; 454 | if (!match) { 455 | return true; 456 | } 457 | relativeOp = relativeExpr[match.nextCombinator]; 458 | el = dir(el, relativeOp.dir); 459 | if (!relativeOp.immediate) { 460 | return { 461 | // advance for non-immediate 462 | el: el, 463 | match: match 464 | }; 465 | } 466 | } else { 467 | relativeOp = relativeExpr[match.nextCombinator]; 468 | if (relativeOp.immediate) { 469 | // retreat but advance startEl 470 | return { 471 | el: dir(startEl, relativeExpr[startMatch.nextCombinator].dir), 472 | match: startMatch 473 | }; 474 | } else { 475 | // advance (before immediate match + jump unmatched) 476 | return { 477 | el: el && dir(el, relativeOp.dir), 478 | match: match 479 | }; 480 | } 481 | } 482 | } while (el); 483 | 484 | // only occur when match immediate 485 | return { 486 | el: dir(startEl, relativeExpr[startMatch.nextCombinator].dir), 487 | match: startMatch 488 | }; 489 | } 490 | 491 | // find fixed part, fixed with seeds 492 | function findFixedMatchFromHead(el, head) { 493 | var relativeOp, 494 | cur = head; 495 | 496 | do { 497 | if (!singleMatch(el, cur)) { 498 | return null; 499 | } 500 | cur = cur.prev; 501 | if (!cur) { 502 | return true; 503 | } 504 | relativeOp = relativeExpr[cur.nextCombinator]; 505 | el = dir(el, relativeOp.dir); 506 | } while (el && relativeOp.immediate); 507 | if (!el) { 508 | return null; 509 | } 510 | return { 511 | el: el, 512 | match: cur 513 | }; 514 | } 515 | 516 | function genId(el) { 517 | var selectorId; 518 | 519 | if (isContextXML) { 520 | if (!(selectorId = el.getAttribute(EXPANDO_SELECTOR_KEY))) { 521 | el.setAttribute(EXPANDO_SELECTOR_KEY, selectorId = (+new Date() + '_' + (++uuid))); 522 | } 523 | } else { 524 | if (!(selectorId = el[EXPANDO_SELECTOR_KEY])) { 525 | selectorId = el[EXPANDO_SELECTOR_KEY] = (+new Date()) + '_' + (++uuid); 526 | } 527 | } 528 | 529 | return selectorId; 530 | } 531 | 532 | function matchSub(el, match) { 533 | var selectorId = genId(el), 534 | matchKey; 535 | matchKey = selectorId + '_' + (match.order || 0); 536 | if (matchKey in subMatchesCache) { 537 | return subMatchesCache[matchKey]; 538 | } 539 | subMatchesCache[matchKey] = matchSubInternal(el, match); 540 | return subMatchesCache[matchKey]; 541 | } 542 | 543 | // recursive match by sub selector string from right to left 544 | // grouped by immediate selectors 545 | function matchSubInternal(el, match) { 546 | var matchImmediateRet = matchImmediate(el, match); 547 | if (matchImmediateRet === true) { 548 | return true; 549 | } else { 550 | el = matchImmediateRet.el; 551 | match = matchImmediateRet.match; 552 | while (el) { 553 | if (matchSub(el, match)) { 554 | return true; 555 | } 556 | el = dir(el, relativeExpr[match.nextCombinator].dir); 557 | } 558 | return false; 559 | } 560 | } 561 | 562 | function select(str, context, seeds) { 563 | if (!caches[str]) { 564 | caches[str] = parser.parse(str); 565 | } 566 | 567 | var selector = caches[str], 568 | groupIndex = 0, 569 | groupLen = selector.length, 570 | contextDocument, 571 | group, 572 | ret = []; 573 | 574 | if (seeds) { 575 | context = context || seeds[0].ownerDocument; 576 | } 577 | 578 | contextDocument = context && context.ownerDocument || typeof document !== 'undefined' && document; 579 | 580 | if (context && context.nodeType === 9 && !contextDocument) { 581 | contextDocument = context; 582 | } 583 | 584 | context = context || contextDocument; 585 | 586 | isContextXML = isXML(context); 587 | 588 | for (; groupIndex < groupLen; groupIndex++) { 589 | resetStatus(); 590 | 591 | group = selector[groupIndex]; 592 | 593 | var suffix = group.suffix, 594 | suffixIndex, 595 | suffixLen, 596 | seedsIndex, 597 | mySeeds = seeds, 598 | seedsLen, 599 | id = null; 600 | 601 | if (!mySeeds) { 602 | if (suffix && !isContextXML) { 603 | suffixIndex = 0; 604 | suffixLen = suffix.length; 605 | for (; suffixIndex < suffixLen; suffixIndex++) { 606 | var singleSuffix = suffix[suffixIndex]; 607 | if (singleSuffix.t === 'id') { 608 | id = singleSuffix.value; 609 | break; 610 | } 611 | } 612 | } 613 | 614 | if (id) { 615 | // http://yiminghe.github.io/lab/playground/fragment-selector/selector.html 616 | var doesNotHasById = !context.getElementById, 617 | contextInDom = util.contains(contextDocument, context), 618 | tmp = doesNotHasById ? ( 619 | contextInDom ? 620 | contextDocument.getElementById(id) : 621 | null 622 | ) : context.getElementById(id); 623 | // id bug 624 | // https://github.com/kissyteam/kissy/issues/67 625 | if (!tmp && doesNotHasById || tmp && getAttr(tmp, 'id') !== id) { 626 | var tmps = util.getElementsByTagName('*', context), 627 | tmpLen = tmps.length, 628 | tmpI = 0; 629 | for (; tmpI < tmpLen; tmpI++) { 630 | tmp = tmps[tmpI]; 631 | if (getAttr(tmp, 'id') === id) { 632 | mySeeds = [tmp]; 633 | break; 634 | } 635 | } 636 | if (tmpI === tmpLen) { 637 | mySeeds = []; 638 | } 639 | } else { 640 | if (contextInDom && tmp && context !== contextDocument) { 641 | tmp = util.contains(context, tmp) ? tmp : null; 642 | } 643 | mySeeds = tmp ? [tmp] : []; 644 | } 645 | } else { 646 | mySeeds = util.getElementsByTagName(group.value || '*', context); 647 | } 648 | } 649 | 650 | seedsIndex = 0; 651 | seedsLen = mySeeds.length; 652 | 653 | if (!seedsLen) { 654 | continue; 655 | } 656 | 657 | for (; seedsIndex < seedsLen; seedsIndex++) { 658 | var seed = mySeeds[seedsIndex]; 659 | var matchHead = findFixedMatchFromHead(seed, group); 660 | if (matchHead === true) { 661 | ret.push(seed); 662 | } else if (matchHead) { 663 | if (matchSub(matchHead.el, matchHead.match)) { 664 | ret.push(seed); 665 | } 666 | } 667 | } 668 | } 669 | 670 | if (groupLen > 1) { 671 | ret = util.unique(ret); 672 | } 673 | 674 | return ret; 675 | } 676 | 677 | export default select; 678 | 679 | select.parse = function (str) { 680 | return parser.parse(str); 681 | }; 682 | 683 | select.matches = matches; 684 | 685 | select.util = util; 686 | /** 687 | * @ignore 688 | * note 2013-03-28 689 | * - use recursive call to replace backtracking algorithm 690 | * 691 | * refer 692 | * - http://www.w3.org/TR/selectors/ 693 | * - http://www.impressivewebs.com/browser-support-css3-selectors/ 694 | * - http://blogs.msdn.com/ie/archive/2010/05/13/the-css-corner-css3-selectors.aspx 695 | * - http://sizzlejs.com/ 696 | */ 697 | -------------------------------------------------------------------------------- /src/query-selector/gen-parser.md: -------------------------------------------------------------------------------- 1 | ``` 2 | node ../../node_modules/kison/bin/kison -g parser-grammar.kison 3 | ``` 4 | -------------------------------------------------------------------------------- /src/query-selector/parser-grammar.kison: -------------------------------------------------------------------------------- 1 | /** 2 | * lalr grammar and lexer rules for css selector. 3 | * refer: http://www.w3.org/TR/selectors/ 4 | * @author yiminghe@gmail.com 5 | */ 6 | (function () { 7 | var escape = '(?:\\\\[^\\n\\r\\f0-9a-f])'; 8 | 9 | var nmstart = '(?:[\\w]|[^\\x00-\\xa0]|' + escape + ')'; 10 | 11 | var nmchar = '(?:[\\w\\d-]|[^\\x00-\\xa0]|' + escape + ')'; 12 | 13 | var ident = '(?:' + nmstart + nmchar + '*)'; 14 | 15 | var name = '(?:' + nmchar + '+)'; 16 | 17 | var w = '(?:[\\t\\r\\n\\f\\x20]*)'; 18 | 19 | var s = '(?:[\\t\\r\\n\\f\\x20]+)'; 20 | 21 | return { 22 | productions: [ 23 | // ---------------------- selectors_group 24 | { 25 | symbol: 'selectors_group', 26 | rhs: ['selector'], 27 | action: function () { 28 | return [this.$1]; 29 | } 30 | }, 31 | { 32 | symbol: 'selectors_group', 33 | rhs: ['selectors_group', 'COMMA', 'selector'], 34 | action: function () { 35 | this.$1.push(this.$3); 36 | } 37 | }, 38 | 39 | // ---------------------- selector 40 | { 41 | symbol: 'selector', 42 | rhs: ['simple_selector_sequence'] 43 | }, 44 | { 45 | symbol: 'selector', 46 | rhs: ['selector', 'combinator', 'simple_selector_sequence'], 47 | action: function () { 48 | // LinkedList 49 | 50 | this.$1.nextCombinator = this.$3.prevCombinator = this.$2; 51 | var order; 52 | order = this.$1.order = this.$1.order || 0; 53 | this.$3.order = order + 1; 54 | this.$3.prev = this.$1; 55 | this.$1.next = this.$3; 56 | return this.$3; 57 | } 58 | }, 59 | 60 | // ---------------------- combinator 61 | { 62 | symbol: 'combinator', 63 | rhs: ['PLUS'] 64 | }, 65 | { 66 | symbol: 'combinator', 67 | rhs: ['GREATER'] 68 | }, 69 | { 70 | symbol: 'combinator', 71 | rhs: ['TILDE'] 72 | }, 73 | { 74 | symbol: 'combinator', 75 | rhs: ['S'], 76 | action: function () { 77 | return ' '; 78 | } 79 | }, 80 | 81 | // ---------------------- type_selector 82 | { 83 | symbol: 'type_selector', 84 | rhs: ['IDENT'], 85 | action: function () { 86 | return { 87 | t: 'tag', 88 | value: this.$1 89 | }; 90 | } 91 | }, 92 | { 93 | symbol: 'type_selector', 94 | rhs: ['UNIVERSAL'], 95 | action: function () { 96 | return { 97 | t: 'tag', 98 | value: this.$1 99 | }; 100 | } 101 | }, 102 | 103 | { 104 | symbol: 'id_selector', 105 | rhs: ['HASH'], 106 | action: function () { 107 | return { 108 | t: 'id', 109 | value: this.$1 110 | }; 111 | } 112 | }, 113 | 114 | { 115 | symbol: 'class_selector', 116 | rhs: ['CLASS'], 117 | action: function () { 118 | return { 119 | t: 'cls', 120 | value: this.$1 121 | }; 122 | } 123 | }, 124 | 125 | // ---------------------- attrib_match 126 | { 127 | symbol: 'attrib_match', 128 | rhs: ['PREFIX_MATCH'] 129 | }, 130 | { 131 | symbol: 'attrib_match', 132 | rhs: ['SUFFIX_MATCH'] 133 | }, 134 | { 135 | symbol: 'attrib_match', 136 | rhs: ['SUBSTRING_MATCH'] 137 | }, 138 | { 139 | symbol: 'attrib_match', 140 | rhs: ['ALL_MATCH'] 141 | }, 142 | { 143 | symbol: 'attrib_match', 144 | rhs: ['INCLUDES'] 145 | }, 146 | { 147 | symbol: 'attrib_match', 148 | rhs: ['DASH_MATCH'] 149 | }, 150 | 151 | // ---------------------- attrib 152 | { 153 | symbol: 'attrib', 154 | rhs: ['LEFT_BRACKET', 'IDENT', 'RIGHT_BRACKET'], 155 | action: function () { 156 | return { 157 | t: 'attrib', 158 | value: { 159 | ident: this.$2 160 | } 161 | }; 162 | } 163 | }, 164 | { 165 | symbol: 'attrib_val', 166 | rhs: ['IDENT'] 167 | }, 168 | { 169 | symbol: 'attrib_val', 170 | rhs: ['STRING'] 171 | }, 172 | { 173 | symbol: 'attrib', 174 | rhs: ['LEFT_BRACKET', 'IDENT', 'attrib_match', 'attrib_val', 'RIGHT_BRACKET'], 175 | action: function () { 176 | return { 177 | t: 'attrib', 178 | value: { 179 | ident: this.$2, 180 | match: this.$3, 181 | value: this.$4 182 | } 183 | }; 184 | } 185 | }, 186 | 187 | // ---------------------- pseudo 188 | { 189 | symbol: 'pseudo', 190 | rhs: ['COLON', 'FUNCTION', 'PARAMETER', 'RIGHT_PARENTHESES'], 191 | action: function () { 192 | return { 193 | t: 'pseudo', 194 | value: { 195 | fn: this.$2.toLowerCase(), 196 | param: this.$3 197 | } 198 | }; 199 | } 200 | }, 201 | { 202 | symbol: 'pseudo', 203 | rhs: ['COLON', 'IDENT'], 204 | action: function () { 205 | return { 206 | t: 'pseudo', 207 | value: { 208 | ident: this.$2.toLowerCase() 209 | } 210 | }; 211 | } 212 | }, 213 | 214 | // ---------------------- negation 215 | { 216 | symbol: 'negation', 217 | rhs: ['NOT', 'negation_arg', 'RIGHT_PARENTHESES'], 218 | action: function () { 219 | return { 220 | t: 'pseudo', 221 | value: { 222 | fn: 'not', 223 | param: this.$2 224 | } 225 | }; 226 | } 227 | }, 228 | { 229 | symbol: 'negation_arg', 230 | rhs: ['type_selector'] 231 | }, 232 | { 233 | symbol: 'negation_arg', 234 | rhs: ['id_selector'] 235 | }, 236 | { 237 | symbol: 'negation_arg', 238 | rhs: ['class_selector'] 239 | }, 240 | { 241 | symbol: 'negation_arg', 242 | rhs: ['attrib'] 243 | }, 244 | { 245 | symbol: 'negation_arg', 246 | rhs: ['pseudo'] 247 | }, 248 | 249 | // ---------------------- suffix_selector 250 | { 251 | symbol: 'suffix_selector', 252 | rhs: ['id_selector'] 253 | }, 254 | { 255 | symbol: 'suffix_selector', 256 | rhs: ['class_selector'] 257 | }, 258 | { 259 | symbol: 'suffix_selector', 260 | rhs: ['attrib'] 261 | }, 262 | { 263 | symbol: 'suffix_selector', 264 | rhs: ['pseudo'] 265 | }, 266 | { 267 | symbol: 'suffix_selector', 268 | rhs: ['negation'] 269 | }, 270 | { 271 | symbol: 'suffix_selectors', 272 | rhs: ['suffix_selector'], 273 | action: function () { 274 | return [ this.$1 ]; 275 | } 276 | }, 277 | { 278 | symbol: 'suffix_selectors', 279 | rhs: ['suffix_selectors', 'suffix_selector'], 280 | action: function () { 281 | this.$1.push(this.$2); 282 | } 283 | }, 284 | 285 | // ---------------------- simple_selector_sequence 286 | { 287 | symbol: 'simple_selector_sequence', 288 | rhs: ['type_selector'] 289 | }, 290 | { 291 | symbol: 'simple_selector_sequence', 292 | rhs: ['suffix_selectors'], 293 | action: function () { 294 | return { 295 | suffix: this.$1 296 | }; 297 | } 298 | }, 299 | { 300 | symbol: 'simple_selector_sequence', 301 | rhs: ['type_selector', 'suffix_selectors'], 302 | action: function () { 303 | return { 304 | t: 'tag', 305 | value: this.$1.value, 306 | suffix: this.$2 307 | }; 308 | } 309 | } 310 | ], 311 | lexer: { 312 | rules: [ 313 | { 314 | regexp: new RegExp('^' + '\\[' + w), 315 | token: 'LEFT_BRACKET', 316 | action: function () { 317 | this.text = this.yy.trim(this.text); 318 | } 319 | }, 320 | { 321 | regexp: new RegExp('^' + w + '\\]'), 322 | token: 'RIGHT_BRACKET', 323 | action: function () { 324 | this.text = this.yy.trim(this.text); 325 | } 326 | }, 327 | { 328 | regexp: new RegExp('^' + w + '~=' + w), 329 | token: 'INCLUDES', 330 | action: function () { 331 | this.text = this.yy.trim(this.text); 332 | } 333 | }, 334 | { 335 | regexp: new RegExp('^' + w + '\\|=' + w), 336 | token: 'DASH_MATCH', 337 | action: function () { 338 | this.text = this.yy.trim(this.text); 339 | } 340 | }, 341 | { 342 | regexp: new RegExp('^' + w + '\\^=' + w), 343 | token: 'PREFIX_MATCH', 344 | action: function () { 345 | this.text = this.yy.trim(this.text); 346 | } 347 | }, 348 | { 349 | regexp: new RegExp('^' + w + '\\$=' + w), 350 | token: 'SUFFIX_MATCH', 351 | action: function () { 352 | this.text = this.yy.trim(this.text); 353 | } 354 | }, 355 | { 356 | regexp: new RegExp('^' + w + '\\*=' + w), 357 | token: 'SUBSTRING_MATCH', 358 | action: function () { 359 | this.text = this.yy.trim(this.text); 360 | } 361 | }, 362 | { 363 | regexp: new RegExp('^' + w + '\\=' + w), 364 | token: 'ALL_MATCH', 365 | action: function () { 366 | this.text = this.yy.trim(this.text); 367 | } 368 | }, 369 | { 370 | regexp: new RegExp('^' + ident + '\\('), 371 | token: 'FUNCTION', 372 | action: function () { 373 | this.text = this.yy.trim(this.text).slice(0, -1); 374 | this.pushState('fn'); 375 | } 376 | }, 377 | { 378 | state: ['fn'], 379 | regexp: /^[^\)]*/, 380 | token: 'PARAMETER', 381 | action: function () { 382 | this.popState(); 383 | } 384 | }, 385 | { 386 | regexp: new RegExp('^' + w + '\\)'), 387 | token: 'RIGHT_PARENTHESES', 388 | action: function () { 389 | this.text = this.yy.trim(this.text); 390 | } 391 | }, 392 | { 393 | regexp: new RegExp('^:not\\(' + w,'i'), 394 | token: 'NOT', 395 | action: function () { 396 | this.text = this.yy.trim(this.text); 397 | } 398 | }, 399 | { 400 | regexp: new RegExp('^' + ident), 401 | token: 'IDENT', 402 | action: function () { 403 | this.text = this.yy.unEscape(this.text); 404 | } 405 | }, 406 | { 407 | regexp: /^"(\\"|[^"])*"/, 408 | token: 'STRING', 409 | action: function () { 410 | this.text = this.yy.unEscapeStr(this.text); 411 | } 412 | }, 413 | { 414 | regexp: /^'(\\'|[^'])*'/, 415 | token: 'STRING', 416 | action: function () { 417 | this.text = this.yy.unEscapeStr(this.text); 418 | } 419 | }, 420 | { 421 | regexp: new RegExp('^#' + name), 422 | token: 'HASH', 423 | action: function () { 424 | this.text = this.yy.unEscape(this.text.slice(1)); 425 | } 426 | }, 427 | { 428 | regexp: new RegExp('^\\.' + ident), 429 | token: 'CLASS', 430 | action: function () { 431 | this.text = this.yy.unEscape(this.text.slice(1)); 432 | } 433 | }, 434 | { 435 | regexp: new RegExp('^' + w + ',' + w), 436 | token: 'COMMA', 437 | action: function () { 438 | this.text = this.yy.trim(this.text); 439 | } 440 | }, 441 | { 442 | regexp: /^::?/, 443 | token: 'COLON' 444 | }, 445 | { 446 | regexp: new RegExp('^' + w + '\\+' + w), 447 | token: 'PLUS', 448 | action: function () { 449 | this.text = this.yy.trim(this.text); 450 | } 451 | }, 452 | { 453 | regexp: new RegExp('^' + w + '>' + w), 454 | token: 'GREATER', 455 | action: function () { 456 | this.text = this.yy.trim(this.text); 457 | } 458 | }, 459 | { 460 | regexp: new RegExp('^' + w + '~' + w), 461 | token: 'TILDE', 462 | action: function () { 463 | this.text = this.yy.trim(this.text); 464 | } 465 | }, 466 | { 467 | regexp: /^\*/, 468 | token: 'UNIVERSAL' 469 | }, 470 | { 471 | regexp: new RegExp('^' + s), 472 | token: 'S' 473 | }, 474 | { 475 | regexp: /^./, 476 | token: 'INVALID' 477 | } 478 | ] 479 | } 480 | }; 481 | })(); -------------------------------------------------------------------------------- /src/query-selector/parser.js: -------------------------------------------------------------------------------- 1 | /* 2 | Generated by kison.*/ 3 | /*jshint quotmark:false, loopfunc:true, indent:false, unused:false, asi:true, boss:true*/ 4 | /* Generated by kison */ 5 | var parser = {}, 6 | GrammarConst = { 7 | SHIFT_TYPE: 1, 8 | REDUCE_TYPE: 2, 9 | ACCEPT_TYPE: 0, 10 | TYPE_INDEX: 0, 11 | PRODUCTION_INDEX: 1, 12 | TO_INDEX: 2, 13 | }; 14 | 15 | /*jslint quotmark: false*/ 16 | function mix(to, from) { 17 | for (var f in from) { 18 | to[f] = from[f]; 19 | } 20 | } 21 | 22 | function isArray(obj) { 23 | return '[object Array]' === Object.prototype.toString.call(obj); 24 | } 25 | 26 | function each(object, fn, context) { 27 | if (object) { 28 | var key, 29 | val, 30 | length, 31 | i = 0; 32 | 33 | context = context || null; 34 | 35 | if (!isArray(object)) { 36 | for (key in object) { 37 | // can not use hasOwnProperty 38 | if (fn.call(context, object[key], key, object) === false) { 39 | break; 40 | } 41 | } 42 | } else { 43 | length = object.length; 44 | for (val = object[0]; i < length; val = object[++i]) { 45 | if (fn.call(context, val, i, object) === false) { 46 | break; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | function inArray(item, arr) { 54 | for (var i = 0, l = arr.length; i < l; i++) { 55 | if (arr[i] === item) { 56 | return true; 57 | } 58 | } 59 | return false; 60 | } 61 | 62 | var Lexer = function Lexer(cfg) { 63 | var self = this; 64 | 65 | /* 66 | lex rules. 67 | @type {Object[]} 68 | @example 69 | [ 70 | { 71 | regexp:'\\w+', 72 | state:['xx'], 73 | token:'c', 74 | // this => lex 75 | action:function(){} 76 | } 77 | ] 78 | */ 79 | self.rules = []; 80 | 81 | mix(self, cfg); 82 | 83 | /* 84 | Input languages 85 | @type {String} 86 | */ 87 | 88 | self.resetInput(self.input); 89 | }; 90 | Lexer.prototype = { 91 | resetInput: function(input) { 92 | mix(this, { 93 | input: input, 94 | matched: '', 95 | stateStack: [Lexer.STATIC.INITIAL], 96 | match: '', 97 | text: '', 98 | firstLine: 1, 99 | lineNumber: 1, 100 | lastLine: 1, 101 | firstColumn: 1, 102 | lastColumn: 1, 103 | }); 104 | }, 105 | getCurrentRules: function() { 106 | var self = this, 107 | currentState = self.stateStack[self.stateStack.length - 1], 108 | rules = []; 109 | //#JSCOVERAGE_IF 110 | if (self.mapState) { 111 | currentState = self.mapState(currentState); 112 | } 113 | each(self.rules, function(r) { 114 | var state = r.state || r[3]; 115 | if (!state) { 116 | if (currentState === Lexer.STATIC.INITIAL) { 117 | rules.push(r); 118 | } 119 | } else if (inArray(currentState, state)) { 120 | rules.push(r); 121 | } 122 | }); 123 | return rules; 124 | }, 125 | pushState: function(state) { 126 | this.stateStack.push(state); 127 | }, 128 | popState: function(num) { 129 | num = num || 1; 130 | var ret; 131 | while (num--) { 132 | ret = this.stateStack.pop(); 133 | } 134 | return ret; 135 | }, 136 | showDebugInfo: function() { 137 | var self = this, 138 | DEBUG_CONTEXT_LIMIT = Lexer.STATIC.DEBUG_CONTEXT_LIMIT, 139 | matched = self.matched, 140 | match = self.match, 141 | input = self.input; 142 | matched = matched.slice(0, matched.length - match.length); 143 | //#JSCOVERAGE_IF 0 144 | var past = 145 | (matched.length > DEBUG_CONTEXT_LIMIT ? '...' : '') + 146 | matched.slice(0 - DEBUG_CONTEXT_LIMIT).replace(/\n/, ' '), 147 | next = match + input; 148 | //#JSCOVERAGE_ENDIF 149 | next = 150 | next.slice(0, DEBUG_CONTEXT_LIMIT) + 151 | (next.length > DEBUG_CONTEXT_LIMIT ? '...' : ''); 152 | return past + next + '\n' + new Array(past.length + 1).join('-') + '^'; 153 | }, 154 | mapSymbol: function mapSymbolForCodeGen(t) { 155 | return this.symbolMap[t]; 156 | }, 157 | mapReverseSymbol: function(rs) { 158 | var self = this, 159 | symbolMap = self.symbolMap, 160 | i, 161 | reverseSymbolMap = self.reverseSymbolMap; 162 | if (!reverseSymbolMap && symbolMap) { 163 | reverseSymbolMap = self.reverseSymbolMap = {}; 164 | for (i in symbolMap) { 165 | reverseSymbolMap[symbolMap[i]] = i; 166 | } 167 | } 168 | //#JSCOVERAGE_IF 169 | if (reverseSymbolMap) { 170 | return reverseSymbolMap[rs]; 171 | } else { 172 | return rs; 173 | } 174 | }, 175 | lex: function() { 176 | var self = this, 177 | input = self.input, 178 | i, 179 | rule, 180 | m, 181 | ret, 182 | lines, 183 | rules = self.getCurrentRules(); 184 | 185 | self.match = self.text = ''; 186 | 187 | if (!input) { 188 | return self.mapSymbol(Lexer.STATIC.END_TAG); 189 | } 190 | 191 | for (i = 0; i < rules.length; i++) { 192 | rule = rules[i]; 193 | //#JSCOVERAGE_IF 0 194 | var regexp = rule.regexp || rule[1], 195 | token = rule.token || rule[0], 196 | action = rule.action || rule[2] || undefined; 197 | //#JSCOVERAGE_ENDIF 198 | if ((m = input.match(regexp))) { 199 | lines = m[0].match(/\n.*/g); 200 | if (lines) { 201 | self.lineNumber += lines.length; 202 | } 203 | mix(self, { 204 | firstLine: self.lastLine, 205 | lastLine: self.lineNumber + 1, 206 | firstColumn: self.lastColumn, 207 | lastColumn: lines 208 | ? lines[lines.length - 1].length - 1 209 | : self.lastColumn + m[0].length, 210 | }); 211 | var match; 212 | // for error report 213 | match = self.match = m[0]; 214 | 215 | // all matches 216 | self.matches = m; 217 | // may change by user 218 | self.text = match; 219 | // matched content utils now 220 | self.matched += match; 221 | ret = action && action.call(self); 222 | if (ret === undefined) { 223 | ret = token; 224 | } else { 225 | ret = self.mapSymbol(ret); 226 | } 227 | input = input.slice(match.length); 228 | self.input = input; 229 | 230 | if (ret) { 231 | return ret; 232 | } else { 233 | // ignore 234 | return self.lex(); 235 | } 236 | } 237 | } 238 | }, 239 | }; 240 | Lexer.STATIC = { 241 | INITIAL: 'I', 242 | DEBUG_CONTEXT_LIMIT: 20, 243 | END_TAG: '$EOF', 244 | }; 245 | var lexer = new Lexer({ 246 | rules: [ 247 | [ 248 | 'b', 249 | /^\[(?:[\t\r\n\f\x20]*)/, 250 | function() { 251 | this.text = this.yy.trim(this.text); 252 | }, 253 | ], 254 | [ 255 | 'c', 256 | /^(?:[\t\r\n\f\x20]*)\]/, 257 | function() { 258 | this.text = this.yy.trim(this.text); 259 | }, 260 | ], 261 | [ 262 | 'd', 263 | /^(?:[\t\r\n\f\x20]*)~=(?:[\t\r\n\f\x20]*)/, 264 | function() { 265 | this.text = this.yy.trim(this.text); 266 | }, 267 | ], 268 | [ 269 | 'e', 270 | /^(?:[\t\r\n\f\x20]*)\|=(?:[\t\r\n\f\x20]*)/, 271 | function() { 272 | this.text = this.yy.trim(this.text); 273 | }, 274 | ], 275 | [ 276 | 'f', 277 | /^(?:[\t\r\n\f\x20]*)\^=(?:[\t\r\n\f\x20]*)/, 278 | function() { 279 | this.text = this.yy.trim(this.text); 280 | }, 281 | ], 282 | [ 283 | 'g', 284 | /^(?:[\t\r\n\f\x20]*)\$=(?:[\t\r\n\f\x20]*)/, 285 | function() { 286 | this.text = this.yy.trim(this.text); 287 | }, 288 | ], 289 | [ 290 | 'h', 291 | /^(?:[\t\r\n\f\x20]*)\*=(?:[\t\r\n\f\x20]*)/, 292 | function() { 293 | this.text = this.yy.trim(this.text); 294 | }, 295 | ], 296 | [ 297 | 'i', 298 | /^(?:[\t\r\n\f\x20]*)\=(?:[\t\r\n\f\x20]*)/, 299 | function() { 300 | this.text = this.yy.trim(this.text); 301 | }, 302 | ], 303 | [ 304 | 'j', 305 | /^(?:(?:[\w]|[^\x00-\xa0]|(?:\\[^\n\r\f0-9a-f]))(?:[\w\d-]|[^\x00-\xa0]|(?:\\[^\n\r\f0-9a-f]))*)\(/, 306 | function() { 307 | this.text = this.yy.trim(this.text).slice(0, -1); 308 | this.pushState('fn'); 309 | }, 310 | ], 311 | [ 312 | 'k', 313 | /^[^\)]*/, 314 | function() { 315 | this.popState(); 316 | }, 317 | ['fn'], 318 | ], 319 | [ 320 | 'l', 321 | /^(?:[\t\r\n\f\x20]*)\)/, 322 | function() { 323 | this.text = this.yy.trim(this.text); 324 | }, 325 | ], 326 | [ 327 | 'm', 328 | /^:not\((?:[\t\r\n\f\x20]*)/i, 329 | function() { 330 | this.text = this.yy.trim(this.text); 331 | }, 332 | ], 333 | [ 334 | 'n', 335 | /^(?:(?:[\w]|[^\x00-\xa0]|(?:\\[^\n\r\f0-9a-f]))(?:[\w\d-]|[^\x00-\xa0]|(?:\\[^\n\r\f0-9a-f]))*)/, 336 | function() { 337 | this.text = this.yy.unEscape(this.text); 338 | }, 339 | ], 340 | [ 341 | 'o', 342 | /^"(\\"|[^"])*"/, 343 | function() { 344 | this.text = this.yy.unEscapeStr(this.text); 345 | }, 346 | ], 347 | [ 348 | 'o', 349 | /^'(\\'|[^'])*'/, 350 | function() { 351 | this.text = this.yy.unEscapeStr(this.text); 352 | }, 353 | ], 354 | [ 355 | 'p', 356 | /^#(?:(?:[\w\d-]|[^\x00-\xa0]|(?:\\[^\n\r\f0-9a-f]))+)/, 357 | function() { 358 | this.text = this.yy.unEscape(this.text.slice(1)); 359 | }, 360 | ], 361 | [ 362 | 'q', 363 | /^\.(?:(?:[\w]|[^\x00-\xa0]|(?:\\[^\n\r\f0-9a-f]))(?:[\w\d-]|[^\x00-\xa0]|(?:\\[^\n\r\f0-9a-f]))*)/, 364 | function() { 365 | this.text = this.yy.unEscape(this.text.slice(1)); 366 | }, 367 | ], 368 | [ 369 | 'r', 370 | /^(?:[\t\r\n\f\x20]*),(?:[\t\r\n\f\x20]*)/, 371 | function() { 372 | this.text = this.yy.trim(this.text); 373 | }, 374 | ], 375 | ['s', /^::?/, 0], 376 | [ 377 | 't', 378 | /^(?:[\t\r\n\f\x20]*)\+(?:[\t\r\n\f\x20]*)/, 379 | function() { 380 | this.text = this.yy.trim(this.text); 381 | }, 382 | ], 383 | [ 384 | 'u', 385 | /^(?:[\t\r\n\f\x20]*)>(?:[\t\r\n\f\x20]*)/, 386 | function() { 387 | this.text = this.yy.trim(this.text); 388 | }, 389 | ], 390 | [ 391 | 'v', 392 | /^(?:[\t\r\n\f\x20]*)~(?:[\t\r\n\f\x20]*)/, 393 | function() { 394 | this.text = this.yy.trim(this.text); 395 | }, 396 | ], 397 | ['w', /^\*/, 0], 398 | ['x', /^(?:[\t\r\n\f\x20]+)/, 0], 399 | ['y', /^./, 0], 400 | ], 401 | }); 402 | parser.lexer = lexer; 403 | lexer.symbolMap = { 404 | $EOF: 'a', 405 | LEFT_BRACKET: 'b', 406 | RIGHT_BRACKET: 'c', 407 | INCLUDES: 'd', 408 | DASH_MATCH: 'e', 409 | PREFIX_MATCH: 'f', 410 | SUFFIX_MATCH: 'g', 411 | SUBSTRING_MATCH: 'h', 412 | ALL_MATCH: 'i', 413 | FUNCTION: 'j', 414 | PARAMETER: 'k', 415 | RIGHT_PARENTHESES: 'l', 416 | NOT: 'm', 417 | IDENT: 'n', 418 | STRING: 'o', 419 | HASH: 'p', 420 | CLASS: 'q', 421 | COMMA: 'r', 422 | COLON: 's', 423 | PLUS: 't', 424 | GREATER: 'u', 425 | TILDE: 'v', 426 | UNIVERSAL: 'w', 427 | S: 'x', 428 | INVALID: 'y', 429 | $START: 'z', 430 | selectors_group: 'aa', 431 | selector: 'ab', 432 | simple_selector_sequence: 'ac', 433 | combinator: 'ad', 434 | type_selector: 'ae', 435 | id_selector: 'af', 436 | class_selector: 'ag', 437 | attrib_match: 'ah', 438 | attrib: 'ai', 439 | attrib_val: 'aj', 440 | pseudo: 'ak', 441 | negation: 'al', 442 | negation_arg: 'am', 443 | suffix_selector: 'an', 444 | suffix_selectors: 'ao', 445 | }; 446 | parser.productions = [ 447 | ['z', ['aa']], 448 | [ 449 | 'aa', 450 | ['ab'], 451 | function() { 452 | return [this.$1]; 453 | }, 454 | ], 455 | [ 456 | 'aa', 457 | ['aa', 'r', 'ab'], 458 | function() { 459 | this.$1.push(this.$3); 460 | }, 461 | ], 462 | ['ab', ['ac']], 463 | [ 464 | 'ab', 465 | ['ab', 'ad', 'ac'], 466 | function() { 467 | // LinkedList 468 | 469 | this.$1.nextCombinator = this.$3.prevCombinator = this.$2; 470 | var order; 471 | order = this.$1.order = this.$1.order || 0; 472 | this.$3.order = order + 1; 473 | this.$3.prev = this.$1; 474 | this.$1.next = this.$3; 475 | return this.$3; 476 | }, 477 | ], 478 | ['ad', ['t']], 479 | ['ad', ['u']], 480 | ['ad', ['v']], 481 | [ 482 | 'ad', 483 | ['x'], 484 | function() { 485 | return ' '; 486 | }, 487 | ], 488 | [ 489 | 'ae', 490 | ['n'], 491 | function() { 492 | return { 493 | t: 'tag', 494 | value: this.$1, 495 | }; 496 | }, 497 | ], 498 | [ 499 | 'ae', 500 | ['w'], 501 | function() { 502 | return { 503 | t: 'tag', 504 | value: this.$1, 505 | }; 506 | }, 507 | ], 508 | [ 509 | 'af', 510 | ['p'], 511 | function() { 512 | return { 513 | t: 'id', 514 | value: this.$1, 515 | }; 516 | }, 517 | ], 518 | [ 519 | 'ag', 520 | ['q'], 521 | function() { 522 | return { 523 | t: 'cls', 524 | value: this.$1, 525 | }; 526 | }, 527 | ], 528 | ['ah', ['f']], 529 | ['ah', ['g']], 530 | ['ah', ['h']], 531 | ['ah', ['i']], 532 | ['ah', ['d']], 533 | ['ah', ['e']], 534 | [ 535 | 'ai', 536 | ['b', 'n', 'c'], 537 | function() { 538 | return { 539 | t: 'attrib', 540 | value: { 541 | ident: this.$2, 542 | }, 543 | }; 544 | }, 545 | ], 546 | ['aj', ['n']], 547 | ['aj', ['o']], 548 | [ 549 | 'ai', 550 | ['b', 'n', 'ah', 'aj', 'c'], 551 | function() { 552 | return { 553 | t: 'attrib', 554 | value: { 555 | ident: this.$2, 556 | match: this.$3, 557 | value: this.$4, 558 | }, 559 | }; 560 | }, 561 | ], 562 | [ 563 | 'ak', 564 | ['s', 'j', 'k', 'l'], 565 | function() { 566 | return { 567 | t: 'pseudo', 568 | value: { 569 | fn: this.$2.toLowerCase(), 570 | param: this.$3, 571 | }, 572 | }; 573 | }, 574 | ], 575 | [ 576 | 'ak', 577 | ['s', 'n'], 578 | function() { 579 | return { 580 | t: 'pseudo', 581 | value: { 582 | ident: this.$2.toLowerCase(), 583 | }, 584 | }; 585 | }, 586 | ], 587 | [ 588 | 'al', 589 | ['m', 'am', 'l'], 590 | function() { 591 | return { 592 | t: 'pseudo', 593 | value: { 594 | fn: 'not', 595 | param: this.$2, 596 | }, 597 | }; 598 | }, 599 | ], 600 | ['am', ['ae']], 601 | ['am', ['af']], 602 | ['am', ['ag']], 603 | ['am', ['ai']], 604 | ['am', ['ak']], 605 | ['an', ['af']], 606 | ['an', ['ag']], 607 | ['an', ['ai']], 608 | ['an', ['ak']], 609 | ['an', ['al']], 610 | [ 611 | 'ao', 612 | ['an'], 613 | function() { 614 | return [this.$1]; 615 | }, 616 | ], 617 | [ 618 | 'ao', 619 | ['ao', 'an'], 620 | function() { 621 | this.$1.push(this.$2); 622 | }, 623 | ], 624 | ['ac', ['ae']], 625 | [ 626 | 'ac', 627 | ['ao'], 628 | function() { 629 | return { 630 | suffix: this.$1, 631 | }; 632 | }, 633 | ], 634 | [ 635 | 'ac', 636 | ['ae', 'ao'], 637 | function() { 638 | return { 639 | t: 'tag', 640 | value: this.$1.value, 641 | suffix: this.$2, 642 | }; 643 | }, 644 | ], 645 | ]; 646 | parser.table = { 647 | gotos: { 648 | '0': { 649 | aa: 8, 650 | ab: 9, 651 | ae: 10, 652 | af: 11, 653 | ag: 12, 654 | ai: 13, 655 | ak: 14, 656 | al: 15, 657 | an: 16, 658 | ao: 17, 659 | ac: 18, 660 | }, 661 | '2': { 662 | ae: 20, 663 | af: 21, 664 | ag: 22, 665 | ai: 23, 666 | ak: 24, 667 | am: 25, 668 | }, 669 | '9': { 670 | ad: 33, 671 | }, 672 | '10': { 673 | af: 11, 674 | ag: 12, 675 | ai: 13, 676 | ak: 14, 677 | al: 15, 678 | an: 16, 679 | ao: 34, 680 | }, 681 | '17': { 682 | af: 11, 683 | ag: 12, 684 | ai: 13, 685 | ak: 14, 686 | al: 15, 687 | an: 35, 688 | }, 689 | '19': { 690 | ah: 43, 691 | }, 692 | '28': { 693 | ab: 46, 694 | ae: 10, 695 | af: 11, 696 | ag: 12, 697 | ai: 13, 698 | ak: 14, 699 | al: 15, 700 | an: 16, 701 | ao: 17, 702 | ac: 18, 703 | }, 704 | '33': { 705 | ae: 10, 706 | af: 11, 707 | ag: 12, 708 | ai: 13, 709 | ak: 14, 710 | al: 15, 711 | an: 16, 712 | ao: 17, 713 | ac: 47, 714 | }, 715 | '34': { 716 | af: 11, 717 | ag: 12, 718 | ai: 13, 719 | ak: 14, 720 | al: 15, 721 | an: 35, 722 | }, 723 | '43': { 724 | aj: 50, 725 | }, 726 | '46': { 727 | ad: 33, 728 | }, 729 | }, 730 | action: { 731 | '0': { 732 | b: [1, undefined, 1], 733 | m: [1, undefined, 2], 734 | n: [1, undefined, 3], 735 | p: [1, undefined, 4], 736 | q: [1, undefined, 5], 737 | s: [1, undefined, 6], 738 | w: [1, undefined, 7], 739 | }, 740 | '1': { 741 | n: [1, undefined, 19], 742 | }, 743 | '2': { 744 | b: [1, undefined, 1], 745 | n: [1, undefined, 3], 746 | p: [1, undefined, 4], 747 | q: [1, undefined, 5], 748 | s: [1, undefined, 6], 749 | w: [1, undefined, 7], 750 | }, 751 | '3': { 752 | a: [2, 9], 753 | r: [2, 9], 754 | t: [2, 9], 755 | u: [2, 9], 756 | v: [2, 9], 757 | x: [2, 9], 758 | p: [2, 9], 759 | q: [2, 9], 760 | b: [2, 9], 761 | s: [2, 9], 762 | m: [2, 9], 763 | l: [2, 9], 764 | }, 765 | '4': { 766 | a: [2, 11], 767 | r: [2, 11], 768 | t: [2, 11], 769 | u: [2, 11], 770 | v: [2, 11], 771 | x: [2, 11], 772 | p: [2, 11], 773 | q: [2, 11], 774 | b: [2, 11], 775 | s: [2, 11], 776 | m: [2, 11], 777 | l: [2, 11], 778 | }, 779 | '5': { 780 | a: [2, 12], 781 | r: [2, 12], 782 | t: [2, 12], 783 | u: [2, 12], 784 | v: [2, 12], 785 | x: [2, 12], 786 | p: [2, 12], 787 | q: [2, 12], 788 | b: [2, 12], 789 | s: [2, 12], 790 | m: [2, 12], 791 | l: [2, 12], 792 | }, 793 | '6': { 794 | j: [1, undefined, 26], 795 | n: [1, undefined, 27], 796 | }, 797 | '7': { 798 | a: [2, 10], 799 | r: [2, 10], 800 | t: [2, 10], 801 | u: [2, 10], 802 | v: [2, 10], 803 | x: [2, 10], 804 | p: [2, 10], 805 | q: [2, 10], 806 | b: [2, 10], 807 | s: [2, 10], 808 | m: [2, 10], 809 | l: [2, 10], 810 | }, 811 | '8': { 812 | a: [0], 813 | r: [1, undefined, 28], 814 | }, 815 | '9': { 816 | a: [2, 1], 817 | r: [2, 1], 818 | t: [1, undefined, 29], 819 | u: [1, undefined, 30], 820 | v: [1, undefined, 31], 821 | x: [1, undefined, 32], 822 | }, 823 | '10': { 824 | a: [2, 38], 825 | r: [2, 38], 826 | t: [2, 38], 827 | u: [2, 38], 828 | v: [2, 38], 829 | x: [2, 38], 830 | b: [1, undefined, 1], 831 | m: [1, undefined, 2], 832 | p: [1, undefined, 4], 833 | q: [1, undefined, 5], 834 | s: [1, undefined, 6], 835 | }, 836 | '11': { 837 | a: [2, 31], 838 | r: [2, 31], 839 | t: [2, 31], 840 | u: [2, 31], 841 | v: [2, 31], 842 | x: [2, 31], 843 | p: [2, 31], 844 | q: [2, 31], 845 | b: [2, 31], 846 | s: [2, 31], 847 | m: [2, 31], 848 | }, 849 | '12': { 850 | a: [2, 32], 851 | r: [2, 32], 852 | t: [2, 32], 853 | u: [2, 32], 854 | v: [2, 32], 855 | x: [2, 32], 856 | p: [2, 32], 857 | q: [2, 32], 858 | b: [2, 32], 859 | s: [2, 32], 860 | m: [2, 32], 861 | }, 862 | '13': { 863 | a: [2, 33], 864 | r: [2, 33], 865 | t: [2, 33], 866 | u: [2, 33], 867 | v: [2, 33], 868 | x: [2, 33], 869 | p: [2, 33], 870 | q: [2, 33], 871 | b: [2, 33], 872 | s: [2, 33], 873 | m: [2, 33], 874 | }, 875 | '14': { 876 | a: [2, 34], 877 | r: [2, 34], 878 | t: [2, 34], 879 | u: [2, 34], 880 | v: [2, 34], 881 | x: [2, 34], 882 | p: [2, 34], 883 | q: [2, 34], 884 | b: [2, 34], 885 | s: [2, 34], 886 | m: [2, 34], 887 | }, 888 | '15': { 889 | a: [2, 35], 890 | r: [2, 35], 891 | t: [2, 35], 892 | u: [2, 35], 893 | v: [2, 35], 894 | x: [2, 35], 895 | p: [2, 35], 896 | q: [2, 35], 897 | b: [2, 35], 898 | s: [2, 35], 899 | m: [2, 35], 900 | }, 901 | '16': { 902 | a: [2, 36], 903 | r: [2, 36], 904 | t: [2, 36], 905 | u: [2, 36], 906 | v: [2, 36], 907 | x: [2, 36], 908 | p: [2, 36], 909 | q: [2, 36], 910 | b: [2, 36], 911 | s: [2, 36], 912 | m: [2, 36], 913 | }, 914 | '17': { 915 | a: [2, 39], 916 | r: [2, 39], 917 | t: [2, 39], 918 | u: [2, 39], 919 | v: [2, 39], 920 | x: [2, 39], 921 | b: [1, undefined, 1], 922 | m: [1, undefined, 2], 923 | p: [1, undefined, 4], 924 | q: [1, undefined, 5], 925 | s: [1, undefined, 6], 926 | }, 927 | '18': { 928 | a: [2, 3], 929 | r: [2, 3], 930 | t: [2, 3], 931 | u: [2, 3], 932 | v: [2, 3], 933 | x: [2, 3], 934 | }, 935 | '19': { 936 | c: [1, undefined, 36], 937 | d: [1, undefined, 37], 938 | e: [1, undefined, 38], 939 | f: [1, undefined, 39], 940 | g: [1, undefined, 40], 941 | h: [1, undefined, 41], 942 | i: [1, undefined, 42], 943 | }, 944 | '20': { 945 | l: [2, 26], 946 | }, 947 | '21': { 948 | l: [2, 27], 949 | }, 950 | '22': { 951 | l: [2, 28], 952 | }, 953 | '23': { 954 | l: [2, 29], 955 | }, 956 | '24': { 957 | l: [2, 30], 958 | }, 959 | '25': { 960 | l: [1, undefined, 44], 961 | }, 962 | '26': { 963 | k: [1, undefined, 45], 964 | }, 965 | '27': { 966 | a: [2, 24], 967 | r: [2, 24], 968 | t: [2, 24], 969 | u: [2, 24], 970 | v: [2, 24], 971 | x: [2, 24], 972 | p: [2, 24], 973 | q: [2, 24], 974 | b: [2, 24], 975 | s: [2, 24], 976 | m: [2, 24], 977 | l: [2, 24], 978 | }, 979 | '28': { 980 | b: [1, undefined, 1], 981 | m: [1, undefined, 2], 982 | n: [1, undefined, 3], 983 | p: [1, undefined, 4], 984 | q: [1, undefined, 5], 985 | s: [1, undefined, 6], 986 | w: [1, undefined, 7], 987 | }, 988 | '29': { 989 | n: [2, 5], 990 | w: [2, 5], 991 | p: [2, 5], 992 | q: [2, 5], 993 | b: [2, 5], 994 | s: [2, 5], 995 | m: [2, 5], 996 | }, 997 | '30': { 998 | n: [2, 6], 999 | w: [2, 6], 1000 | p: [2, 6], 1001 | q: [2, 6], 1002 | b: [2, 6], 1003 | s: [2, 6], 1004 | m: [2, 6], 1005 | }, 1006 | '31': { 1007 | n: [2, 7], 1008 | w: [2, 7], 1009 | p: [2, 7], 1010 | q: [2, 7], 1011 | b: [2, 7], 1012 | s: [2, 7], 1013 | m: [2, 7], 1014 | }, 1015 | '32': { 1016 | n: [2, 8], 1017 | w: [2, 8], 1018 | p: [2, 8], 1019 | q: [2, 8], 1020 | b: [2, 8], 1021 | s: [2, 8], 1022 | m: [2, 8], 1023 | }, 1024 | '33': { 1025 | b: [1, undefined, 1], 1026 | m: [1, undefined, 2], 1027 | n: [1, undefined, 3], 1028 | p: [1, undefined, 4], 1029 | q: [1, undefined, 5], 1030 | s: [1, undefined, 6], 1031 | w: [1, undefined, 7], 1032 | }, 1033 | '34': { 1034 | a: [2, 40], 1035 | r: [2, 40], 1036 | t: [2, 40], 1037 | u: [2, 40], 1038 | v: [2, 40], 1039 | x: [2, 40], 1040 | b: [1, undefined, 1], 1041 | m: [1, undefined, 2], 1042 | p: [1, undefined, 4], 1043 | q: [1, undefined, 5], 1044 | s: [1, undefined, 6], 1045 | }, 1046 | '35': { 1047 | a: [2, 37], 1048 | r: [2, 37], 1049 | t: [2, 37], 1050 | u: [2, 37], 1051 | v: [2, 37], 1052 | x: [2, 37], 1053 | p: [2, 37], 1054 | q: [2, 37], 1055 | b: [2, 37], 1056 | s: [2, 37], 1057 | m: [2, 37], 1058 | }, 1059 | '36': { 1060 | a: [2, 19], 1061 | r: [2, 19], 1062 | t: [2, 19], 1063 | u: [2, 19], 1064 | v: [2, 19], 1065 | x: [2, 19], 1066 | p: [2, 19], 1067 | q: [2, 19], 1068 | b: [2, 19], 1069 | s: [2, 19], 1070 | m: [2, 19], 1071 | l: [2, 19], 1072 | }, 1073 | '37': { 1074 | n: [2, 17], 1075 | o: [2, 17], 1076 | }, 1077 | '38': { 1078 | n: [2, 18], 1079 | o: [2, 18], 1080 | }, 1081 | '39': { 1082 | n: [2, 13], 1083 | o: [2, 13], 1084 | }, 1085 | '40': { 1086 | n: [2, 14], 1087 | o: [2, 14], 1088 | }, 1089 | '41': { 1090 | n: [2, 15], 1091 | o: [2, 15], 1092 | }, 1093 | '42': { 1094 | n: [2, 16], 1095 | o: [2, 16], 1096 | }, 1097 | '43': { 1098 | n: [1, undefined, 48], 1099 | o: [1, undefined, 49], 1100 | }, 1101 | '44': { 1102 | a: [2, 25], 1103 | r: [2, 25], 1104 | t: [2, 25], 1105 | u: [2, 25], 1106 | v: [2, 25], 1107 | x: [2, 25], 1108 | p: [2, 25], 1109 | q: [2, 25], 1110 | b: [2, 25], 1111 | s: [2, 25], 1112 | m: [2, 25], 1113 | }, 1114 | '45': { 1115 | l: [1, undefined, 51], 1116 | }, 1117 | '46': { 1118 | a: [2, 2], 1119 | r: [2, 2], 1120 | t: [1, undefined, 29], 1121 | u: [1, undefined, 30], 1122 | v: [1, undefined, 31], 1123 | x: [1, undefined, 32], 1124 | }, 1125 | '47': { 1126 | a: [2, 4], 1127 | r: [2, 4], 1128 | t: [2, 4], 1129 | u: [2, 4], 1130 | v: [2, 4], 1131 | x: [2, 4], 1132 | }, 1133 | '48': { 1134 | c: [2, 20], 1135 | }, 1136 | '49': { 1137 | c: [2, 21], 1138 | }, 1139 | '50': { 1140 | c: [1, undefined, 52], 1141 | }, 1142 | '51': { 1143 | a: [2, 23], 1144 | r: [2, 23], 1145 | t: [2, 23], 1146 | u: [2, 23], 1147 | v: [2, 23], 1148 | x: [2, 23], 1149 | p: [2, 23], 1150 | q: [2, 23], 1151 | b: [2, 23], 1152 | s: [2, 23], 1153 | m: [2, 23], 1154 | l: [2, 23], 1155 | }, 1156 | '52': { 1157 | a: [2, 22], 1158 | r: [2, 22], 1159 | t: [2, 22], 1160 | u: [2, 22], 1161 | v: [2, 22], 1162 | x: [2, 22], 1163 | p: [2, 22], 1164 | q: [2, 22], 1165 | b: [2, 22], 1166 | s: [2, 22], 1167 | m: [2, 22], 1168 | l: [2, 22], 1169 | }, 1170 | }, 1171 | }; 1172 | parser.parse = function parse(input, filename) { 1173 | var self = this, 1174 | lexer = self.lexer, 1175 | state, 1176 | symbol, 1177 | action, 1178 | table = self.table, 1179 | gotos = table.gotos, 1180 | tableAction = table.action, 1181 | productions = self.productions, 1182 | valueStack = [null], 1183 | // for debug info 1184 | prefix = filename ? 'in file: ' + filename + ' ' : '', 1185 | stack = [0]; 1186 | 1187 | lexer.resetInput(input); 1188 | 1189 | while (1) { 1190 | // retrieve state number from top of stack 1191 | state = stack[stack.length - 1]; 1192 | 1193 | if (!symbol) { 1194 | symbol = lexer.lex(); 1195 | } 1196 | 1197 | if (symbol) { 1198 | // read action for current state and first input 1199 | action = tableAction[state] && tableAction[state][symbol]; 1200 | } else { 1201 | action = null; 1202 | } 1203 | 1204 | if (!action) { 1205 | var expected = [], 1206 | error; 1207 | //#JSCOVERAGE_IF 1208 | if (tableAction[state]) { 1209 | for (var symbolForState in tableAction[state]) { 1210 | expected.push(self.lexer.mapReverseSymbol(symbolForState)); 1211 | } 1212 | } 1213 | error = 1214 | prefix + 1215 | 'syntax error at line ' + 1216 | lexer.lineNumber + 1217 | ':\n' + 1218 | lexer.showDebugInfo() + 1219 | '\n' + 1220 | 'expect ' + 1221 | expected.join(', '); 1222 | throw new Error(error); 1223 | } 1224 | 1225 | switch (action[GrammarConst.TYPE_INDEX]) { 1226 | case GrammarConst.SHIFT_TYPE: 1227 | stack.push(symbol); 1228 | 1229 | valueStack.push(lexer.text); 1230 | 1231 | // push state 1232 | stack.push(action[GrammarConst.TO_INDEX]); 1233 | 1234 | // allow to read more 1235 | symbol = null; 1236 | 1237 | break; 1238 | 1239 | case GrammarConst.REDUCE_TYPE: 1240 | var production = productions[action[GrammarConst.PRODUCTION_INDEX]], 1241 | reducedSymbol = production.symbol || production[0], 1242 | reducedAction = production.action || production[2], 1243 | reducedRhs = production.rhs || production[1], 1244 | len = reducedRhs.length, 1245 | i = 0, 1246 | ret, 1247 | $$ = valueStack[valueStack.length - len]; // default to $$ = $1 1248 | 1249 | ret = undefined; 1250 | 1251 | self.$$ = $$; 1252 | 1253 | for (; i < len; i++) { 1254 | self['$' + (len - i)] = valueStack[valueStack.length - 1 - i]; 1255 | } 1256 | 1257 | if (reducedAction) { 1258 | ret = reducedAction.call(self); 1259 | } 1260 | 1261 | if (ret !== undefined) { 1262 | $$ = ret; 1263 | } else { 1264 | $$ = self.$$; 1265 | } 1266 | 1267 | stack = stack.slice(0, -1 * len * 2); 1268 | valueStack = valueStack.slice(0, -1 * len); 1269 | 1270 | stack.push(reducedSymbol); 1271 | 1272 | valueStack.push($$); 1273 | 1274 | var newState = gotos[stack[stack.length - 2]][stack[stack.length - 1]]; 1275 | 1276 | stack.push(newState); 1277 | 1278 | break; 1279 | 1280 | case GrammarConst.ACCEPT_TYPE: 1281 | return $$; 1282 | } 1283 | } 1284 | }; 1285 | export default parser; 1286 | -------------------------------------------------------------------------------- /src/query-selector/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * attr fix for old ie 3 | * @author yiminghe@gmail.com 4 | */ 5 | var R_BOOLEAN = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, 6 | R_FOCUSABLE = /^(?:button|input|object|select|textarea)$/i, 7 | R_CLICKABLE = /^a(?:rea)?$/i, 8 | R_INVALID_CHAR = /:|^on/; 9 | 10 | var attrFix = {}, 11 | propFix, 12 | attrHooks = { 13 | // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ 14 | tabindex: { 15 | get: function (el) { 16 | // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set 17 | var attributeNode = el.getAttributeNode('tabindex'); 18 | return attributeNode && attributeNode.specified ? 19 | parseInt(attributeNode.value, 10) : 20 | R_FOCUSABLE.test(el.nodeName) || 21 | R_CLICKABLE.test(el.nodeName) && el.href ? 22 | 0 : 23 | undefined; 24 | } 25 | } 26 | }, 27 | boolHook = { 28 | get: function (elem, name) { 29 | // 转发到 prop 方法 30 | return elem[propFix[name] || name] ? 31 | // 根据 w3c attribute , true 时返回属性名字符串 32 | name.toLowerCase() : 33 | undefined; 34 | } 35 | }, 36 | attrNodeHook = {}; 37 | 38 | attrHooks.style = { 39 | get: function (el) { 40 | return el.style.cssText; 41 | } 42 | }; 43 | 44 | propFix = { 45 | hidefocus: 'hideFocus', 46 | tabindex: 'tabIndex', 47 | readonly: 'readOnly', 48 | 'for': 'htmlFor', 49 | 'class': 'className', 50 | maxlength: 'maxLength', 51 | cellspacing: 'cellSpacing', 52 | cellpadding: 'cellPadding', 53 | rowspan: 'rowSpan', 54 | colspan: 'colSpan', 55 | usemap: 'useMap', 56 | frameborder: 'frameBorder', 57 | contenteditable: 'contentEditable' 58 | }; 59 | 60 | var ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''; 61 | var doc = typeof document !== 'undefined' ? document : {}; 62 | 63 | function numberify(s) { 64 | var c = 0; 65 | // convert '1.2.3.4' to 1.234 66 | return parseFloat(s.replace(/\./g, function () { 67 | return (c++ === 0) ? '.' : ''; 68 | })); 69 | } 70 | 71 | function ieVersion() { 72 | var m, v; 73 | if ((m = ua.match(/MSIE ([^;]*)|Trident.*; rv(?:\s|:)?([0-9.]+)/)) && 74 | (v = (m[1] || m[2]))) { 75 | return doc.documentMode || numberify(v); 76 | } 77 | } 78 | 79 | function mix(s, t) { 80 | for (var p in t) { 81 | s[p] = t[p]; 82 | } 83 | } 84 | 85 | function each(arr, fn) { 86 | var i = 0, l = arr.length; 87 | for (; i < l; i++) { 88 | if (fn(arr[i], i) === false) { 89 | break; 90 | } 91 | } 92 | } 93 | var ie = ieVersion(); 94 | 95 | if (ie && ie < 8) { 96 | attrHooks.style.set = function (el, val) { 97 | el.style.cssText = val; 98 | }; 99 | 100 | // get attribute value from attribute node for ie 101 | mix(attrNodeHook, { 102 | get: function (elem, name) { 103 | var ret = elem.getAttributeNode(name); 104 | // Return undefined if attribute node specified by user 105 | return ret && ( 106 | // fix #100 107 | ret.specified || ret.nodeValue) ? 108 | ret.nodeValue : 109 | undefined; 110 | } 111 | }); 112 | 113 | // ie6,7 不区分 attribute 与 property 114 | mix(attrFix, propFix); 115 | 116 | // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ 117 | attrHooks.tabIndex = attrHooks.tabindex; 118 | 119 | // 不光是 href, src, 还有 rowspan 等非 mapping 属性,也需要用第 2 个参数来获取原始值 120 | // 注意 colSpan rowSpan 已经由 propFix 转为大写 121 | each(['href', 'src', 'width', 'height', 'colSpan', 'rowSpan'], function (name) { 122 | attrHooks[name] = { 123 | get: function (elem) { 124 | var ret = elem.getAttribute(name, 2); 125 | return ret === null ? undefined : ret; 126 | } 127 | }; 128 | }); 129 | 130 | attrHooks.placeholder = { 131 | get: function (elem, name) { 132 | return elem[name] || attrNodeHook.get(elem, name); 133 | } 134 | }; 135 | } 136 | 137 | if (ie) { 138 | var hrefFix = attrHooks.href = attrHooks.href || {}; 139 | hrefFix.set = function (el, val, name) { 140 | var childNodes = el.childNodes, 141 | b, 142 | len = childNodes.length, 143 | allText = len > 0; 144 | for (len = len - 1; len >= 0; len--) { 145 | if (childNodes[len].nodeType !== 3) { 146 | allText = 0; 147 | } 148 | } 149 | if (allText) { 150 | b = el.ownerDocument.createElement('b'); 151 | b.style.display = 'none'; 152 | el.appendChild(b); 153 | } 154 | el.setAttribute(name, '' + val); 155 | if (b) { 156 | el.removeChild(b); 157 | } 158 | }; 159 | } 160 | 161 | var RE_TRIM = /^[\s\xa0]+|[\s\xa0]+$/g, 162 | trim = String.prototype.trim; 163 | var SPACE = ' '; 164 | 165 | var getElementsByTagName; 166 | getElementsByTagName = function (name, context) { 167 | return context.getElementsByTagName(name); 168 | }; 169 | 170 | if (doc.createElement) { 171 | var div = doc.createElement('div'); 172 | div.appendChild(document.createComment('')); 173 | if (div.getElementsByTagName('*').length) { 174 | getElementsByTagName = function (name, context) { 175 | var nodes = context.getElementsByTagName(name), 176 | needsFilter = name === '*'; 177 | // 178 | if (needsFilter || typeof nodes.length !== 'number') { 179 | var ret = [], 180 | i = 0, 181 | el; 182 | while ((el = nodes[i++])) { 183 | if (!needsFilter || el.nodeType === 1) { 184 | ret.push(el); 185 | } 186 | } 187 | return ret; 188 | } else { 189 | return nodes; 190 | } 191 | }; 192 | } 193 | } 194 | 195 | var compareNodeOrder = ('sourceIndex' in (doc && doc.documentElement || {})) ? function (a, b) { 196 | return a.sourceIndex - b.sourceIndex; 197 | } : function (a, b) { 198 | if (!a.compareDocumentPosition || !b.compareDocumentPosition) { 199 | return a.compareDocumentPosition ? -1 : 1; 200 | } 201 | var bit = a.compareDocumentPosition(b) & 4; 202 | return bit ? -1 : 1; 203 | }; 204 | 205 | var util = { 206 | ie: ie, 207 | 208 | unique: (function () { 209 | var hasDuplicate, 210 | baseHasDuplicate = true; 211 | 212 | // Here we check if the JavaScript engine is using some sort of 213 | // optimization where it does not always call our comparison 214 | // function. If that is the case, discard the hasDuplicate value. 215 | // Thus far that includes Google Chrome. 216 | [0, 0].sort(function () { 217 | baseHasDuplicate = false; 218 | return 0; 219 | }); 220 | 221 | function sortOrder(a, b) { 222 | if (a === b) { 223 | hasDuplicate = true; 224 | return 0; 225 | } 226 | 227 | return compareNodeOrder(a, b); 228 | } 229 | 230 | // 排序去重 231 | return function (elements) { 232 | hasDuplicate = baseHasDuplicate; 233 | elements.sort(sortOrder); 234 | 235 | if (hasDuplicate) { 236 | var i = 1, len = elements.length; 237 | while (i < len) { 238 | if (elements[i] === elements[i - 1]) { 239 | elements.splice(i, 1); 240 | --len; 241 | } else { 242 | i++; 243 | } 244 | } 245 | } 246 | return elements; 247 | }; 248 | })(), 249 | 250 | getElementsByTagName: getElementsByTagName, 251 | 252 | getSimpleAttr: function (el, name) { 253 | var ret = el && el.getAttributeNode(name); 254 | if (ret && ret.specified) { 255 | return 'value' in ret ? ret.value : ret.nodeValue; 256 | } 257 | return undefined; 258 | }, 259 | 260 | contains: ie ? function (a, b) { 261 | if (a.nodeType === 9) { 262 | a = a.documentElement; 263 | } 264 | // !a.contains => a===document || text 265 | // 注意原生 contains 判断时 a===b 也返回 true 266 | b = b.parentNode; 267 | 268 | if (a === b) { 269 | return true; 270 | } 271 | 272 | // when b is document, a.contains(b) 不支持的接口 in ie 273 | if (b && b.nodeType === 1) { 274 | return a.contains && a.contains(b); 275 | } else { 276 | return false; 277 | } 278 | } : function (a, b) { 279 | return !!(a.compareDocumentPosition(b) & 16); 280 | }, 281 | 282 | isTag: function (el, value) { 283 | return value === '*' || el.nodeName.toLowerCase() === value.toLowerCase(); 284 | }, 285 | 286 | hasSingleClass: function (el, cls) { 287 | // consider xml 288 | // https://github.com/kissyteam/kissy/issues/532 289 | var className = el && util.getSimpleAttr(el, 'class'); 290 | return className && (className = className.replace(/[\r\t\n]/g, SPACE)) && 291 | (SPACE + className + SPACE).indexOf(SPACE + cls + SPACE) > -1; 292 | }, 293 | 294 | startsWith: function (str, prefix) { 295 | return str.lastIndexOf(prefix, 0) === 0; 296 | }, 297 | 298 | endsWith: function (str, suffix) { 299 | var ind = str.length - suffix.length; 300 | return ind >= 0 && str.indexOf(suffix, ind) === ind; 301 | }, 302 | 303 | trim: trim ? 304 | function (str) { 305 | return str == null ? '' : trim.call(str); 306 | } : 307 | function (str) { 308 | return str == null ? '' : (str + '').replace(RE_TRIM, ''); 309 | }, 310 | 311 | attr: function (el, name) { 312 | var attrNormalizer, ret; 313 | // scrollLeft 314 | name = name.toLowerCase(); 315 | // custom attrs 316 | name = attrFix[name] || name; 317 | if (R_BOOLEAN.test(name)) { 318 | attrNormalizer = boolHook; 319 | } else if (R_INVALID_CHAR.test(name)) { 320 | // only old ie? 321 | attrNormalizer = attrNodeHook; 322 | } else { 323 | attrNormalizer = attrHooks[name]; 324 | } 325 | if (el && el.nodeType === 1) { 326 | // browsers index elements by id/name on forms, give priority to attributes. 327 | if (el.nodeName.toLowerCase() === 'form') { 328 | attrNormalizer = attrNodeHook; 329 | } 330 | if (attrNormalizer && attrNormalizer.get) { 331 | return attrNormalizer.get(el, name); 332 | } 333 | ret = el.getAttribute(name); 334 | if (ret === '') { 335 | var attrNode = el.getAttributeNode(name); 336 | if (!attrNode || !attrNode.specified) { 337 | return undefined; 338 | } 339 | } 340 | // standard browser non-existing attribute return null 341 | // ie<8 will return undefined , because it return property 342 | // so norm to undefined 343 | return ret === null ? undefined : ret; 344 | } 345 | } 346 | }; 347 | 348 | export default util; 349 | --------------------------------------------------------------------------------