xast.js 2.7 KB

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