dom5
Version:
Utilities for working with parse5 ASTs
184 lines (161 loc) • 4.52 kB
text/typescript
/**
* @license
* Copyright (c) 2018 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
*/
import {ASTNode as Node} from 'parse5';
import {getAttribute, getAttributeIndex, getTextContent} from './util';
export {ASTNode as Node} from 'parse5';
/**
* 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: string): Predicate {
return function(node) {
return getTextContent(node) === value;
};
}
export type Predicate = (node: Node) => boolean;
/**
* OR an array of predicates
*/
function OR(...predicates: Predicate[]): Predicate;
function OR(/* ...rules */): Predicate {
const rules = new Array<Predicate>(arguments.length);
for (let i = 0; i < arguments.length; i++) {
rules[i] = arguments[i];
}
return function(node) {
for (let i = 0; i < rules.length; i++) {
if (rules[i](node)) {
return true;
}
}
return false;
};
}
/**
* AND an array of predicates
*/
function AND(...predicates: Predicate[]): Predicate;
function AND(/* ...rules */): Predicate {
const rules = new Array<Predicate>(arguments.length);
for (let i = 0; i < arguments.length; i++) {
rules[i] = arguments[i];
}
return function(node) {
for (let 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: Predicate): Predicate {
return function(node) {
return !predicateFn(node);
};
}
/**
* Returns a predicate that matches any node with a parent matching
* `predicateFn`.
*/
function parentMatches(predicateFn: Predicate): Predicate {
return function(node) {
let parent = node.parentNode;
while (parent !== undefined) {
if (predicateFn(parent)) {
return true;
}
parent = parent.parentNode;
}
return false;
};
}
function hasAttr(attr: string): Predicate {
return function(node) {
return getAttributeIndex(node, attr) > -1;
};
}
function hasAttrValue(attr: string, value: string): Predicate {
return function(node) {
return getAttribute(node, attr) === value;
};
}
function hasClass(name: string): Predicate {
return hasSpaceSeparatedAttrValue('class', name);
}
function hasTagName(name: string): Predicate {
const 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: RegExp): Predicate {
return function(node) {
if (!node.tagName) {
return false;
}
return regex.test(node.tagName.toLowerCase());
};
}
export function hasSpaceSeparatedAttrValue(
name: string, value: string): Predicate {
return function(element: Node) {
const attributeValue = getAttribute(element, name);
if (typeof attributeValue !== 'string') {
return false;
}
return attributeValue.split(' ').indexOf(value) !== -1;
};
}
export function isDocument(node: Node): boolean {
return node.nodeName === '#document';
}
export function isDocumentFragment(node: Node): boolean {
return node.nodeName === '#document-fragment';
}
export function isElement(node: Node): boolean {
return node.nodeName === node.tagName;
}
export function isTextNode(node: Node): boolean {
return node.nodeName === '#text';
}
export function isCommentNode(node: Node): boolean {
return node.nodeName === '#comment';
}
export const predicates = {
hasClass: hasClass,
hasAttr: hasAttr,
hasAttrValue: hasAttrValue,
hasMatchingTagName: hasMatchingTagName,
hasSpaceSeparatedAttrValue: hasSpaceSeparatedAttrValue,
hasTagName: hasTagName,
hasTextValue: hasTextValue,
AND: AND,
OR: OR,
NOT: NOT,
parentMatches: parentMatches,
};