如何遍历和HTML AST in Javascript 其中根节点其实是一个数组

How to traverse and HTML AST in Javascript where the root node is actually an array

我正在尝试找到遍历 HTML AST 并找到具有 type: tag 的所有节点并将它们推入数组的正确方法。

此外,我正在使用 html-parse-stringify 将我的 HTML 转换为 AST,如果有帮助的话。

我在 youtube 上看过一些关于遍历 HTML AST 的视频,但它们都是以一个对象作为主要起始节点开始的,而我是从一个数组开始的。但怀疑这是一个很大的问题。

我正在处理的数据集是网站抓取的数据,然后使用前面提到的库将其转换为 AST。

从这里我只想创建一个基本的循环结构,它可以完全遍历我的 AST,同时过滤掉所有不必要的类型,例如 text & comment,然后将正确的对象推入数组。

这是我正在使用的数据结构,为了便于复制,我放置了一个空数据结构。

另外,为了时间复杂度,我想尽可能减少循环的使用。

function mainLoop(node) {
  Array.prototype.forEach.call(node, parent => {
    console.log(parent.name);
    const children = parent.children.filter(n => n.type !== 'text' && n.type !== 'comment');
    loop(children)
  })
}

function loop(children) {
  console.log(children.name)
  if (children) {
    Array.prototype.forEach.call(children, child => {
      loop(child);
    })
  }
}

mainLoop();

空数据结构

const docTree = [
  {
    attrs: {
      class: "flex flex-col h-screen",
    },
    children: [
      {
        type: 'tag',
        name: 'main',
        attrs: {
          class: ''
        },
        children: [],
      }
    ],
    name: 'div',
    type: 'tag',
    voidElement: false,
  }
]

所以我想我已经找到了我正在寻找的解决方案。尚未完全完成测试,但大致如此。

它使用一个外部循环遍历我的初始元素数组,然后使用内部递归函数循环遍历我正在寻找的所有子数据并将其推入一个数组。

function parentLoop(domAST) {
  let results = [];
  Array.prototype.forEach.call(domAST, ele => {
    function childLoop(node) {
      const cleaned = node.children.filter(n => n.type !== 'text' && n.type !== 'comment' && n.name !== 'br');
      for (let i = 0; i < cleaned.length; i++) {
        let child = cleaned[i];
        if (child.type === 'tag') {
          results.push(child);
        }
        childLoop(child);
      }
    }
    childLoop(ele);
  })
  return results;
}

如果有更好或更清洁的解决方案,我仍然欢迎他们。

如果您的唯一目标是删除文本和评论,那么在一次 reduce 中就非常简单:

const traverse = (nodes) => {
  return nodes.reduce((acc,node) => {
     if(node.type === 'text' || node.type === 'comment') return acc;
     return [ ...acc, { ...node, children: traverse(node.children) } ]
  },[]);
}

我实际上 运行 这个代码,但我认为它会工作

如果你想压平所有 children 那么你这样做:

const traverse = (nodes) => {
  return nodes.reduce((acc,{children = [], ...node}) => {
     if(node.type === 'text' || node.type === 'comment') return acc;
     return [ ...acc, node, ...traverse(children) ]
  },[]);
}

编辑 2:啊,我错过了您只需要类型标签的部分。这样就完成了:

const traverse = (nodes) => {
  return nodes.reduce((acc,{children = [], ...node}) => {
     if(node.type !== 'tag') return acc;
     return [ ...acc, node, ...traverse(children) ]
  },[]);
}

此外,我不确定您是否希望 children 保留为 parent 节点的一部分。这可能也是你想要的:

const traverse = (nodes) => {
  return nodes.reduce((acc,node) => {
     if(node.type !== 'tag') return acc;
     return [ ...acc, node, ...traverse(node.children) ]
  },[]);
}