[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | /**
|
---|
| 4 | * @typedef {import('./types').XastNode} XastNode
|
---|
| 5 | * @typedef {import('./types').XastChild} XastChild
|
---|
| 6 | * @typedef {import('./types').XastParent} XastParent
|
---|
| 7 | * @typedef {import('./types').Visitor} Visitor
|
---|
| 8 | */
|
---|
| 9 |
|
---|
| 10 | const { selectAll, selectOne, is } = require('css-select');
|
---|
| 11 | const xastAdaptor = require('./svgo/css-select-adapter.js');
|
---|
| 12 |
|
---|
| 13 | const cssSelectOptions = {
|
---|
| 14 | xmlMode: true,
|
---|
| 15 | adapter: xastAdaptor,
|
---|
| 16 | };
|
---|
| 17 |
|
---|
| 18 | /**
|
---|
| 19 | * @type {(node: XastNode, selector: string) => Array<XastChild>}
|
---|
| 20 | */
|
---|
| 21 | const querySelectorAll = (node, selector) => {
|
---|
| 22 | return selectAll(selector, node, cssSelectOptions);
|
---|
| 23 | };
|
---|
| 24 | exports.querySelectorAll = querySelectorAll;
|
---|
| 25 |
|
---|
| 26 | /**
|
---|
| 27 | * @type {(node: XastNode, selector: string) => null | XastChild}
|
---|
| 28 | */
|
---|
| 29 | const querySelector = (node, selector) => {
|
---|
| 30 | return selectOne(selector, node, cssSelectOptions);
|
---|
| 31 | };
|
---|
| 32 | exports.querySelector = querySelector;
|
---|
| 33 |
|
---|
| 34 | /**
|
---|
| 35 | * @type {(node: XastChild, selector: string) => boolean}
|
---|
| 36 | */
|
---|
| 37 | const matches = (node, selector) => {
|
---|
| 38 | return is(node, selector, cssSelectOptions);
|
---|
| 39 | };
|
---|
| 40 | exports.matches = matches;
|
---|
| 41 |
|
---|
| 42 | /**
|
---|
| 43 | * @type {(node: XastChild, name: string) => null | XastChild}
|
---|
| 44 | */
|
---|
| 45 | const closestByName = (node, name) => {
|
---|
| 46 | let currentNode = node;
|
---|
| 47 | while (currentNode) {
|
---|
| 48 | if (currentNode.type === 'element' && currentNode.name === name) {
|
---|
| 49 | return currentNode;
|
---|
| 50 | }
|
---|
| 51 | // @ts-ignore parentNode is hidden from public usage
|
---|
| 52 | currentNode = currentNode.parentNode;
|
---|
| 53 | }
|
---|
| 54 | return null;
|
---|
| 55 | };
|
---|
| 56 | exports.closestByName = closestByName;
|
---|
| 57 |
|
---|
| 58 | const visitSkip = Symbol();
|
---|
| 59 | exports.visitSkip = visitSkip;
|
---|
| 60 |
|
---|
| 61 | /**
|
---|
| 62 | * @type {(node: XastNode, visitor: Visitor, parentNode?: any) => void}
|
---|
| 63 | */
|
---|
| 64 | const visit = (node, visitor, parentNode) => {
|
---|
| 65 | const callbacks = visitor[node.type];
|
---|
| 66 | if (callbacks && callbacks.enter) {
|
---|
| 67 | // @ts-ignore hard to infer
|
---|
| 68 | const symbol = callbacks.enter(node, parentNode);
|
---|
| 69 | if (symbol === visitSkip) {
|
---|
| 70 | return;
|
---|
| 71 | }
|
---|
| 72 | }
|
---|
| 73 | // visit root children
|
---|
| 74 | if (node.type === 'root') {
|
---|
| 75 | // copy children array to not loose cursor when children is spliced
|
---|
| 76 | for (const child of node.children) {
|
---|
| 77 | visit(child, visitor, node);
|
---|
| 78 | }
|
---|
| 79 | }
|
---|
| 80 | // visit element children if still attached to parent
|
---|
| 81 | if (node.type === 'element') {
|
---|
| 82 | if (parentNode.children.includes(node)) {
|
---|
| 83 | for (const child of node.children) {
|
---|
| 84 | visit(child, visitor, node);
|
---|
| 85 | }
|
---|
| 86 | }
|
---|
| 87 | }
|
---|
| 88 | if (callbacks && callbacks.exit) {
|
---|
| 89 | // @ts-ignore hard to infer
|
---|
| 90 | callbacks.exit(node, parentNode);
|
---|
| 91 | }
|
---|
| 92 | };
|
---|
| 93 | exports.visit = visit;
|
---|
| 94 |
|
---|
| 95 | /**
|
---|
| 96 | * @type {(node: XastChild, parentNode: XastParent) => void}
|
---|
| 97 | */
|
---|
| 98 | const detachNodeFromParent = (node, parentNode) => {
|
---|
| 99 | // avoid splice to not break for loops
|
---|
| 100 | parentNode.children = parentNode.children.filter((child) => child !== node);
|
---|
| 101 | };
|
---|
| 102 | exports.detachNodeFromParent = detachNodeFromParent;
|
---|