From 38fbaf8f92bb0562e8aac6d3cd2beb77e55cef39 Mon Sep 17 00:00:00 2001 From: Daniel Freedman Date: Mon, 16 Mar 2015 13:41:33 -0700 Subject: [PATCH] Generic nodeWalk and nodeWalkAll apis query and queryAll moved to shells that just walk elements using `node` functions --- dom5.js | 51 +++++++++++++++++++++++++++++++++++++++++------ test/dom5_test.js | 49 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/dom5.js b/dom5.js index c2dd78859..c7c387159 100644 --- a/dom5.js +++ b/dom5.js @@ -29,6 +29,9 @@ function hasAttribute(element, name) { return getAttributeIndex(element, name) !== -1; } +/** + * @returns {string|null} The string value of attribute `name`, or `null`. + */ function getAttribute(element, name) { var i = getAttributeIndex(element, name); if (i > -1) { @@ -124,14 +127,21 @@ function isElement(node) { return node.nodeName === node.tagName; } -function query(node, predicate) { - if (isElement(node) && predicate(node)) { +/** + * Walk the tree down from `node`, applying the `predicate` function. + * Return the first node that matches the given predicate. + * + * @returns {Node} `null` if no node matches, parse5 node object if a node + * matches + */ +function nodeWalk(node, predicate) { + if (predicate(node)) { return node; } var match = null; if (node.childNodes) { for (var i = 0; i < node.childNodes.length; i++) { - match = query(node.childNodes[i], predicate); + match = nodeWalk(node.childNodes[i], predicate); if (match) { break; } @@ -140,21 +150,48 @@ function query(node, predicate) { return match; } -function queryAll(node, predicate, matches) { +/** + * Walk the tree down from `node`, applying the `predicate` function. + * All nodes matching the predicate function from `node` to leaves will be + * returned. + * + * @returns {Array[Node]} + */ +function nodeWalkAll(node, predicate, matches) { if (!matches) { matches = []; } - if (isElement(node) && predicate(node)) { + if (predicate(node)) { matches.push(node); } if (node.childNodes) { for (var i = 0; i < node.childNodes.length; i++) { - queryAll(node.childNodes[i], predicate, matches); + nodeWalkAll(node.childNodes[i], predicate, matches); } } return matches; } +/** + * Equivalent to `nodeWalk`, but only matches elements + * + * @returns {Element} + */ +function query(node, predicate) { + var elementPredicate = AND(isElement, predicate); + return nodeWalk(node, elementPredicate); +} + +/** + * Equivalent to `nodeWalkAll`, but only matches elements + * + * @return {Array[Element]} + */ +function queryAll(node, predicate, matches) { + var elementPredicate = AND(isElement, predicate); + return nodeWalkAll(node, elementPredicate, matches); +} + module.exports = { getAttribute: getAttribute, hasAttribute: hasAttribute, @@ -162,6 +199,8 @@ module.exports = { isElement: isElement, query: query, queryAll: queryAll, + nodeWalk: nodeWalk, + nodeWalkAll: nodeWalkAll, predicates: { hasClass: hasClass, hasAttr: hasAttr, diff --git a/test/dom5_test.js b/test/dom5_test.js index a3158dfbd..e8cf11548 100644 --- a/test/dom5_test.js +++ b/test/dom5_test.js @@ -216,7 +216,9 @@ suite('dom5', function() { '', '', '', '' @@ -227,6 +229,33 @@ suite('dom5', function() { doc = parser.parse(docText); }); + test('nodeWalk', function() { + var textNode = function(node) { + if (node.nodeName === '#text') { + return node.value === '\nsample element\n'; + } + return false; + }; + + var comment = function(node) { + return node.nodeName === '#comment'; + }; + + // doc -> body -> dom-module -> template -> template.content + var templateContent = doc.childNodes[1].childNodes[1].childNodes[0] + .childNodes[1].childNodes[0]; + + // 'sample element' text node + var expected = templateContent.childNodes[4]; + var actual = dom5.nodeWalk(doc, textNode); + assert.equal(expected, actual); + + // + expected = templateContent.childNodes[5]; + actual = dom5.nodeWalk(templateContent, comment); + assert.equal(expected, actual); + }); + test('query', function() { var fn = dom5.predicates.AND( dom5.predicates.hasTagName('link'), @@ -238,6 +267,24 @@ suite('dom5', function() { assert.equal(expected, actual); }); + test('nodeWalkAll', function() { + var empty = function(node) { + if (node.nodeName === '#text') { + return !/\S/.test(node.value); + } + return false; + }; + + // serialize to count for inserted and + var serializedDoc = (new Parse5.Serializer()).serialize(doc); + // subtract one to get "gap" number + var expected = serializedDoc.split('\n').length - 1; + // add two for normalized text node "\nsample text\n" + var actual = dom5.nodeWalkAll(doc, empty).length + 2; + + assert.equal(expected, actual); + }); + test('queryAll', function() { var fn = dom5.predicates.AND( dom5.predicates.OR(