UNPKG

@open-wc/building-utils

Version:
636 lines (634 loc) 17.6 kB
/* eslint-disable */ // @ts-nocheck 'use strict'; /** * @license * Copyright (c) 2015 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ var cloneObject = require('clone'); var parse5_1 = require('parse5'); function getAttributeIndex(element, name) { if (!element.attrs) { return -1; } var n = name.toLowerCase(); for (var i = 0; i < element.attrs.length; i++) { if (element.attrs[i].name.toLowerCase() === n) { return i; } } return -1; } /** * @returns `true` iff [element] has the attribute [name], `false` otherwise. */ function hasAttribute(element, name) { return getAttributeIndex(element, name) !== -1; } module.exports.hasAttribute = hasAttribute; function hasSpaceSeparatedAttrValue(name, value) { return function (element) { var attributeValue = getAttribute(element, name); if (typeof attributeValue !== 'string') { return false; } return attributeValue.split(' ').indexOf(value) !== -1; }; } module.exports.hasSpaceSeparatedAttrValue = hasSpaceSeparatedAttrValue; /** * @returns The string value of attribute `name`, or `null`. */ function getAttribute(element, name) { var i = getAttributeIndex(element, name); if (i > -1) { return element.attrs[i].value; } return null; } module.exports.getAttribute = getAttribute; function setAttribute(element, name, value) { var i = getAttributeIndex(element, name); if (i > -1) { element.attrs[i].value = value; } else { element.attrs.push({ name: name, value: value }); } } module.exports.setAttribute = setAttribute; function removeAttribute(element, name) { var i = getAttributeIndex(element, name); if (i > -1) { element.attrs.splice(i, 1); } } module.exports.removeAttribute = removeAttribute; function hasTagName(name) { var n = name.toLowerCase(); return function (node) { if (!node.tagName) { return false; } return node.tagName.toLowerCase() === n; }; } /** * Returns true if `regex.match(tagName)` finds a match. * * This will use the lowercased tagName for comparison. */ function hasMatchingTagName(regex) { return function (node) { if (!node.tagName) { return false; } return regex.test(node.tagName.toLowerCase()); }; } function hasClass(name) { return hasSpaceSeparatedAttrValue('class', name); } function collapseTextRange(parent, start, end) { if (!parent.childNodes) { return; } var text = ''; for (var i = start; i <= end; i++) { text += getTextContent(parent.childNodes[i]); } parent.childNodes.splice(start, end - start + 1); if (text) { var tn = newTextNode(text); tn.parentNode = parent; parent.childNodes.splice(start, 0, tn); } } /** * Normalize the text inside an element * * Equivalent to `element.normalize()` in the browser * See https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize */ function normalize(node) { if (!(isElement(node) || isDocument(node) || isDocumentFragment(node))) { return; } if (!node.childNodes) { return; } var textRangeStart = -1; for (var i = node.childNodes.length - 1, n = void 0; i >= 0; i--) { n = node.childNodes[i]; if (isTextNode(n)) { if (textRangeStart === -1) { textRangeStart = i; } if (i === 0) { // collapse leading text nodes collapseTextRange(node, 0, textRangeStart); } } else { // recurse normalize(n); // collapse the range after this node if (textRangeStart > -1) { collapseTextRange(node, i + 1, textRangeStart); textRangeStart = -1; } } } } module.exports.normalize = normalize; /** * Return the text value of a node or element * * Equivalent to `node.textContent` in the browser */ function getTextContent(node) { if (isCommentNode(node)) { return node.data || ''; } if (isTextNode(node)) { return node.value || ''; } var subtree = nodeWalkAll(node, isTextNode); return subtree.map(getTextContent).join(''); } module.exports.getTextContent = getTextContent; /** * Set the text value of a node or element * * Equivalent to `node.textContent = value` in the browser */ function setTextContent(node, value) { if (isCommentNode(node)) { node.data = value; } else if (isTextNode(node)) { node.value = value; } else { var tn = newTextNode(value); tn.parentNode = node; node.childNodes = [tn]; } } module.exports.setTextContent = setTextContent; /** * Match the text inside an element, textnode, or comment * * Note: nodeWalkAll with hasTextValue may return an textnode and its parent if * the textnode is the only child in that parent. */ function hasTextValue(value) { return function (node) { return getTextContent(node) === value; }; } function OR() { var rules = new Array(arguments.length); for (var i = 0; i < arguments.length; i++) { rules[i] = arguments[i]; } return function (node) { for (var i = 0; i < rules.length; i++) { if (rules[i](node)) { return true; } } return false; }; } function AND() { var rules = new Array(arguments.length); for (var i = 0; i < arguments.length; i++) { rules[i] = arguments[i]; } return function (node) { for (var i = 0; i < rules.length; i++) { if (!rules[i](node)) { return false; } } return true; }; } /** * negate an individual predicate, or a group with AND or OR */ function NOT(predicateFn) { return function (node) { return !predicateFn(node); }; } /** * Returns a predicate that matches any node with a parent matching * `predicateFn`. */ function parentMatches(predicateFn) { return function (node) { var parent = node.parentNode; while (parent !== undefined) { if (predicateFn(parent)) { return true; } parent = parent.parentNode; } return false; }; } function hasAttr(attr) { return function (node) { return getAttributeIndex(node, attr) > -1; }; } function hasAttrValue(attr, value) { return function (node) { return getAttribute(node, attr) === value; }; } function isDocument(node) { return node.nodeName === '#document'; } module.exports.isDocument = isDocument; function isDocumentFragment(node) { return node.nodeName === '#document-fragment'; } module.exports.isDocumentFragment = isDocumentFragment; function isElement(node) { return node.nodeName === node.tagName; } module.exports.isElement = isElement; function isTextNode(node) { return node.nodeName === '#text'; } module.exports.isTextNode = isTextNode; function isCommentNode(node) { return node.nodeName === '#comment'; } module.exports.isCommentNode = isCommentNode; /** * Applies `mapfn` to `node` and the tree below `node`, returning a flattened * list of results. */ function treeMap(node, mapfn) { var results = []; nodeWalk(node, function (node) { results = results.concat(mapfn(node)); return false; }); return results; } module.exports.treeMap = treeMap; module.exports.defaultChildNodes = function (node) { return node.childNodes; }; module.exports.childNodesIncludeTemplate = function (node) { if (node.nodeName === 'template') { return parse5_1.treeAdapters['default'].getTemplateContent(node).childNodes; } return node.childNodes; }; /** * Walk the tree down from `node`, applying the `predicate` function. * Return the first node that matches the given predicate. * * @returns `null` if no node matches, parse5 node object if a node matches. */ function nodeWalk(node, predicate, getChildNodes) { if (getChildNodes === void 0) { getChildNodes = module.exports.defaultChildNodes; } if (predicate(node)) { return node; } var match = null; var childNodes = getChildNodes(node); if (childNodes) { for (var i = 0; i < childNodes.length; i++) { match = nodeWalk(childNodes[i], predicate, getChildNodes); if (match) { break; } } } return match; } module.exports.nodeWalk = nodeWalk; /** * Walk the tree down from `node`, applying the `predicate` function. * All nodes matching the predicate function from `node` to leaves will be * returned. */ function nodeWalkAll(node, predicate, matches, getChildNodes) { if (getChildNodes === void 0) { getChildNodes = module.exports.defaultChildNodes; } if (!matches) { matches = []; } if (predicate(node)) { matches.push(node); } var childNodes = getChildNodes(node); if (childNodes) { for (var i = 0; i < childNodes.length; i++) { nodeWalkAll(childNodes[i], predicate, matches, getChildNodes); } } return matches; } module.exports.nodeWalkAll = nodeWalkAll; function _reverseNodeWalkAll(node, predicate, matches, getChildNodes) { if (getChildNodes === void 0) { getChildNodes = module.exports.defaultChildNodes; } if (!matches) { matches = []; } var childNodes = getChildNodes(node); if (childNodes) { for (var i = childNodes.length - 1; i >= 0; i--) { nodeWalkAll(childNodes[i], predicate, matches, getChildNodes); } } if (predicate(node)) { matches.push(node); } return matches; } /** * Equivalent to `nodeWalk`, but only returns nodes that are either * ancestors or earlier cousins/siblings in the document. * * Nodes are searched in reverse document order, starting from the sibling * prior to `node`. */ function nodeWalkPrior(node, predicate) { // Search our earlier siblings and their descendents. var parent = node.parentNode; if (parent && parent.childNodes) { var idx = parent.childNodes.indexOf(node); var siblings = parent.childNodes.slice(0, idx); for (var i = siblings.length - 1; i >= 0; i--) { var sibling = siblings[i]; if (predicate(sibling)) { return sibling; } var found = nodeWalk(sibling, predicate); if (found) { return found; } } if (predicate(parent)) { return parent; } return nodeWalkPrior(parent, predicate); } return undefined; } module.exports.nodeWalkPrior = nodeWalkPrior; /** * Walk the tree up from the parent of `node`, to its grandparent and so on to * the root of the tree. Return the first ancestor that matches the given * predicate. */ function nodeWalkAncestors(node, predicate) { var parent = node.parentNode; if (!parent) { return undefined; } if (predicate(parent)) { return parent; } return nodeWalkAncestors(parent, predicate); } module.exports.nodeWalkAncestors = nodeWalkAncestors; /** * Equivalent to `nodeWalkAll`, but only returns nodes that are either * ancestors or earlier cousins/siblings in the document. * * Nodes are returned in reverse document order, starting from `node`. */ function nodeWalkAllPrior(node, predicate, matches) { if (!matches) { matches = []; } if (predicate(node)) { matches.push(node); } // Search our earlier siblings and their descendents. var parent = node.parentNode; if (parent) { var idx = parent.childNodes.indexOf(node); var siblings = parent.childNodes.slice(0, idx); for (var i = siblings.length - 1; i >= 0; i--) { _reverseNodeWalkAll(siblings[i], predicate, matches); } nodeWalkAllPrior(parent, predicate, matches); } return matches; } module.exports.nodeWalkAllPrior = nodeWalkAllPrior; /** * Equivalent to `nodeWalk`, but only matches elements */ function query(node, predicate, getChildNodes) { if (getChildNodes === void 0) { getChildNodes = module.exports.defaultChildNodes; } var elementPredicate = AND(isElement, predicate); return nodeWalk(node, elementPredicate, getChildNodes); } module.exports.query = query; /** * Equivalent to `nodeWalkAll`, but only matches elements */ function queryAll(node, predicate, matches, getChildNodes) { if (getChildNodes === void 0) { getChildNodes = module.exports.defaultChildNodes; } var elementPredicate = AND(isElement, predicate); return nodeWalkAll(node, elementPredicate, matches, getChildNodes); } module.exports.queryAll = queryAll; function newTextNode(value) { return { nodeName: '#text', value: value, parentNode: undefined, attrs: [], __location: undefined, }; } function newCommentNode(comment) { return { nodeName: '#comment', data: comment, parentNode: undefined, attrs: [], __location: undefined, }; } function newElement(tagName, namespace) { return { nodeName: tagName, tagName: tagName, childNodes: [], namespaceURI: namespace || 'http://www.w3.org/1999/xhtml', attrs: [], parentNode: undefined, __location: undefined, }; } function newDocumentFragment() { return { nodeName: '#document-fragment', childNodes: [], parentNode: undefined, quirksMode: false, // TODO(rictic): update parse5 typings upstream to mention that attrs and // __location are optional and not always present. attrs: undefined, __location: null, }; } function cloneNode(node) { // parent is a backreference, and we don't want to clone the whole tree, so // make it null before cloning. var parent = node.parentNode; node.parentNode = undefined; var clone = cloneObject(node); node.parentNode = parent; return clone; } module.exports.cloneNode = cloneNode; /** * Inserts `newNode` into `parent` at `index`, optionally replaceing the * current node at `index`. If `newNode` is a DocumentFragment, its childNodes * are inserted and removed from the fragment. */ function insertNode(parent, index, newNode, replace) { if (!parent.childNodes) { parent.childNodes = []; } var newNodes = []; var removedNode = replace ? parent.childNodes[index] : null; if (newNode) { if (isDocumentFragment(newNode)) { if (newNode.childNodes) { newNodes = Array.from(newNode.childNodes); newNode.childNodes.length = 0; } } else { newNodes = [newNode]; remove(newNode); } } if (replace) { removedNode = parent.childNodes[index]; } Array.prototype.splice.apply(parent.childNodes, [index, replace ? 1 : 0].concat(newNodes)); newNodes.forEach(function (n) { n.parentNode = parent; }); if (removedNode) { removedNode.parentNode = undefined; } } function replace(oldNode, newNode) { var parent = oldNode.parentNode; var index = parent.childNodes.indexOf(oldNode); insertNode(parent, index, newNode, true); } module.exports.replace = replace; function remove(node) { var parent = node.parentNode; if (parent && parent.childNodes) { var idx = parent.childNodes.indexOf(node); parent.childNodes.splice(idx, 1); } node.parentNode = undefined; } module.exports.remove = remove; function insertBefore(parent, target, newNode) { var index = parent.childNodes.indexOf(target); insertNode(parent, index, newNode); } module.exports.insertBefore = insertBefore; function insertAfter(parent, target, newNode) { var index = parent.childNodes.indexOf(target); insertNode(parent, index + 1, newNode); } module.exports.insertAfter = insertAfter; /** * Removes a node and places its children in its place. If the node * has no parent, the operation is impossible and no action takes place. */ function removeNodeSaveChildren(node) { // We can't save the children if there's no parent node to provide // for them. var fosterParent = node.parentNode; if (!fosterParent) { return; } var children = (node.childNodes || []).slice(); for (var _i = 0, children_1 = children; _i < children_1.length; _i++) { var child = children_1[_i]; insertBefore(node.parentNode, node, child); } remove(node); } module.exports.removeNodeSaveChildren = removeNodeSaveChildren; /** * When parse5 parses an HTML document with `parse`, it injects missing root * elements (html, head and body) if they are missing. This function removes * these from the AST if they have no location info, so it requires that * the `parse5.parse` be used with the `locationInfo` option of `true`. */ function removeFakeRootElements(ast) { var injectedNodes = queryAll( ast, AND(function (node) { return !node.__location; }, hasMatchingTagName(/^(html|head|body)$/i)), undefined, // Don't descend past 3 levels 'document > html > head|body' function (node) { return node.parentNode && node.parentNode.parentNode ? undefined : node.childNodes; }, ); injectedNodes.reverse().forEach(removeNodeSaveChildren); } module.exports.removeFakeRootElements = removeFakeRootElements; function append(parent, newNode) { var index = (parent.childNodes && parent.childNodes.length) || 0; insertNode(parent, index, newNode); } module.exports.append = append; module.exports.predicates = { hasClass: hasClass, hasAttr: hasAttr, hasAttrValue: hasAttrValue, hasMatchingTagName: hasMatchingTagName, hasSpaceSeparatedAttrValue: hasSpaceSeparatedAttrValue, hasTagName: hasTagName, hasTextValue: hasTextValue, AND: AND, OR: OR, NOT: NOT, parentMatches: parentMatches, }; module.exports.constructors = { text: newTextNode, comment: newCommentNode, element: newElement, fragment: newDocumentFragment, };