diff --git a/javascript/atoms/test/find_elements_typescript_test.html b/javascript/atoms/test/find_elements_typescript_test.html index 7964397d6c25c..f55acbeb04f6a 100644 --- a/javascript/atoms/test/find_elements_typescript_test.html +++ b/javascript/atoms/test/find_elements_typescript_test.html @@ -134,6 +134,28 @@ assert.equal(elements[0].getAttribute('id'), 'input1'); }); + QUnit.test('finds elements by name with double-quote in value', function(assert) { + var el = document.createElement('input'); + el.setAttribute('name', 'my"input'); + el.setAttribute('id', 'quoted-name-input'); + fixture.appendChild(el); + var elements = findElements({'name': 'my"input'}, fixture); + assert.equal(elements.length, 1); + assert.equal(elements[0].getAttribute('id'), 'quoted-name-input'); + fixture.removeChild(el); + }); + + QUnit.test('finds elements by name with backslash in value', function(assert) { + var el = document.createElement('input'); + el.setAttribute('name', 'my\\input'); + el.setAttribute('id', 'backslash-name-input'); + fixture.appendChild(el); + var elements = findElements({'name': 'my\\input'}, fixture); + assert.equal(elements.length, 1); + assert.equal(elements[0].getAttribute('id'), 'backslash-name-input'); + fixture.removeChild(el); + }); + QUnit.test('finds elements by xpath', function(assert) { var elements = findElements({'xpath': './/p'}, fixture); assert.equal(elements.length, 3); @@ -145,6 +167,11 @@ assert.equal(elements[0].getAttribute('id'), 'para2'); }); + QUnit.test('finds elements by xpath using axis notation', function(assert) { + var elements = findElements({'xpath': 'descendant::p'}, fixture); + assert.equal(elements.length, 3); + }); + QUnit.test('throws for unsupported strategy', function(assert) { assert.throws(function() { findElements({'unsupported': 'value'}, fixture); diff --git a/javascript/atoms/typescript/find-elements.ts b/javascript/atoms/typescript/find-elements.ts index 612f4d85bed8e..ff886790b4693 100644 --- a/javascript/atoms/typescript/find-elements.ts +++ b/javascript/atoms/typescript/find-elements.ts @@ -85,7 +85,7 @@ } function nameMany(target: string, root: Root): Element[] { - return Array.from(root.querySelectorAll('*')).filter(el => el.getAttribute('name') === target) + return Array.from(root.querySelectorAll('[name="' + target.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]')) } function tagNameMany(target: string, root: Root): Element[] { @@ -108,26 +108,30 @@ return [] } try { - const reversedNs: Record = {} - const allNodes = doc.getElementsByTagName('*') - for (let i = 0; i < allNodes.length; i++) { - const n = allNodes[i] - const ns = n.namespaceURI - if (ns && !reversedNs[ns]) { - let prefix = n.lookupPrefix(ns) - if (!prefix) { - const m = ns.match('.*/(\\w+)/?$') - prefix = m ? m[1] : 'xhtml' + // Namespace prefixes require a colon in the XPath expression. Skip the + // expensive full-DOM scan when the expression contains no colon at all. + let resolver: ((prefix: string | null) => string | null) | null = null + if (target.indexOf(':') !== -1) { + const reversedNs: Record = {} + const allNodes = doc.getElementsByTagName('*') + for (let i = 0; i < allNodes.length; i++) { + const n = allNodes[i] + const ns = n.namespaceURI + if (ns && !reversedNs[ns]) { + let prefix = n.lookupPrefix(ns) + if (!prefix) { + const m = ns.match('.*/(\\w+)/?$') + prefix = m ? m[1] : 'xhtml' + } + reversedNs[ns] = prefix! } - reversedNs[ns] = prefix! } + const namespaces: Record = {} + for (const key in reversedNs) { + namespaces[reversedNs[key]] = key + } + resolver = (prefix: string | null): string | null => namespaces[prefix || ''] || null } - const namespaces: Record = {} - for (const key in reversedNs) { - namespaces[reversedNs[key]] = key - } - let resolver: XPathNSResolver | ((prefix: string | null) => string | null) = - (prefix: string | null): string | null => namespaces[prefix || ''] || null let result: XPathResult | null = null try {