Javascript 如何从节点列表递归构造JSON层次结构?
考虑到以下输入:Javascript 如何从节点列表递归构造JSON层次结构?,javascript,recursion,Javascript,Recursion,考虑到以下输入: <dl> <dt> <h3>Title A</h3> <dl> <dt> <h3>Title A- A</h3> <dl> <dt><a href="#">Item</a></dt> <dt>
<dl>
<dt>
<h3>Title A</h3>
<dl>
<dt>
<h3>Title A- A</h3>
<dl>
<dt><a href="#">Item</a></dt>
<dt><a href="#">Item</a></dt>
</dl>
</dt>
<dt><a href="#">Item</a></dt>
<dt><a href="#">Item</a></dt>
<dt><a href="#">Item</a></dt>
<dt><a href="#">Item</a></dt>
<dt>
<h3>Title B- A</h3>
<dl>
<dt><a href="#">Item</a></dt>
<dt><a href="#">Item</a></dt>
</dl>
</dt>
<dt><a href="#">Item</a></dt>
</dl>
</dt>
</dl>
以下是我迄今为止所尝试的:
function buildTree(node) {
if (!node) return [];
const h3 = node.querySelector('h3') || node.querySelector('a');
let result = {
title: h3.innerText,
children: []
};
const array = [...node.querySelectorAll('dl')];
if (array) {
result.children = array.map(el => buildTree(el.querySelector('dt')));
}
return result;
}
我得到的结果与我预期的不同,以下是我得到的结果:
{
"title": "Title A",
"children": [
{
"title": "Title A",
"children": [
{
"title": "Title A- A",
"children": [
{
"title": "Item A- A 1",
"children": []
}
]
},
{
"title": "Item A- A 1",
"children": []
},
{
"title": "Title B- A 1",
"children": []
}
]
},
{
"title": "Title A- A",
"children": [
{
"title": "Item A- A 1",
"children": []
}
]
},
{
"title": "Item A- A 1",
"children": []
},
{
"title": "Title B- A 1",
"children": []
}
]
}
似乎有些数据不存在,你知道我可能遗漏了什么吗?我认为一个重要的部分是querySelector和querySelectorAll是递归的。可能是你混淆了dl和dt,因为dl中有多个dt?以下内容对你有用吗
function buildTree(node) {
if (!node) return [];
const h3 = node.querySelector(':scope > h3') || node.querySelector(':scope > a');
let result = {
title: h3.innerText,
children: []
};
const array = [...node.querySelectorAll(':scope > dl > dt')];
result.children = array.map(el => buildTree(el));
return result;
}
您必须首先传递一个dt节点才能使其工作。()你最好在递归之前做出决定:
函数构建树(节点){
const result={};
for(节点子节点的常数){
开关(el.nodeName){
案例“H3”:
案例“A”:
result.title=el.innerText;
结果:儿童=[];
打破
案例“DL”:
result.children=buildTree(el);
打破
案例“DT”:
结果.children.push(buildTree(el));
打破
违约:
warn(`Unknown node type'${el.nodeName}`,el);
}
}
返回结果;
}
在这个例子中,我可以看到您试图解析几乎相同的
DT
s和DL
s。这是一个明确的相互递归案例。如果我们区分如何处理DL
和如何处理DT
,这是一个简单的过程。(正如其他人所指出的,没有任何DD
,这是一种奇怪的结构。)
我在初始的DL
中添加了一个id
,以便于掌握。但是,无论您选择如何获取这个根元素,您都应该能够将它传递给handleDl
,以恢复您的结构
const handleDl=(dl)=>
[…dl.儿童]
.filter(({nodeName})=>nodeName=='DT')
.地图(handleDt)
常数handleDt=(dt)=>{
const kids=[…dt.children]
const h3=kids.find(({nodeName})=>nodeName=='h3')
const dl=kids.find(({nodeName})=>nodeName=='dl')
返回h3
?{title:h3.textContent,children:handleDl(dl)}
:{title:dt.textContent}
}
const node=document.getElementById('foo')
console.log(handleDl(节点))
。作为控制台包装{最大高度:70%!重要;顶部:30%}
标题A-A-B-A
修复html
首先,我要指出,您误用了dl
。从-
HTML
元素表示一个描述列表。元素包含一组术语(使用
元素指定)和描述(由
元素提供)的列表
下面是正确使用dl
、dt
和dd
的情况-
标题1
标题1.1
标题1.6
请注意,它与输出的预期形状相匹配-
{
“标题”:“标题1”,
“儿童”:[
{
“标题”:“标题1.1”,
“儿童”:[
{“标题”:“第1.1.1项”},
{“标题”:“第1.1.2项”}
]
},
{“标题”:“第1.2项”},
{“标题”:“第1.3项”},
{“标题”:“第1.4项”},
{“标题”:“第1.5项”},
{
“标题”:“标题1.6”,
“儿童”:[
{“标题”:“第1.6.1项”},
{“标题”:“第1.6.2项”}
]
},
{“标题”:“项目1.7”}
]
}
fromHtml 如果您不愿意(或不能)如上所述更改输入html,请参阅Scott的精彩答案。要为提议的html编写一个程序,我将把它分成两部分。首先,我们用一个简单的递归形式从HTML编写
-
函数fromHtml(e)
{开关(e?.tagName)
{案例“DL”:
返回Array.from(e.childNodes,fromHtml).flat()
案例“DD”:
return[Array.from(e.childNodes,fromHtml).flat()]
案例“DT”:
案例“A”:
返回e.textContent
违约:
返回[]
}
}
fromHtml(document.querySelector('dl'))
这给了我们这个中间格式-
[
“标题1”,
[
“标题1.1”,
[“项目1.1.1”],
[“第1.1.2项”]
],
[“项目1.2”],
[“项目1.3”],
[“项目1.4”],
[“项目1.5”],
[
“标题1.6”,
[“第1.6.1项],
[“第1.6.2项”]
],
[“项目1.7”]
]
applyLabels 接下来,我将编写一个单独的
applyLabels
函数,该函数添加所需的title
和子标签-
const applyLabels=([title,…children])=>
儿童身高
? {标题,子对象:children.map(applyLabels)}
:{title}
常数结果=
ApplyLabel(来自HTML(document.querySelector('dl'))
{
“标题”:“标题1”,
“儿童”:[
{
“标题”:“标题1.1”,
“儿童”:[
{“标题”:“第1.1.1项”},
{“标题”:“第1.1.2项”}
]
},
{“标题”:“第1.2项”},
{“标题”:“第1.3项”},
{“标题”:“第1.4项”},
{“标题”:“第1.5项”},
{
“标题”:“标题1.6”,
“儿童”:[
{“标题”:“第1.6.1项”},
{“标题”:“第1.6.2项”}
]
},
{“标题”:“项目1.7”}
]
}
我可能会建议最后一个更改,它保证输出中的所有节点具有统一的形状,{title,children}
。这是一个值得注意的变化,因为在本例中,applyLabels
更易于编写,而且性能更好-
const applyLabels=([title,…children])=>
({标题,子项:children.map(applyLabels)})
是的,这意味着最深的子代将有一个空的子代:[]
属性,但它使使用数据变得更容易,因为我们不必对某些属性进行空检查
演示
展开下面的代码段,在您自己的浏览器中验证HTML和applyLabels的结果-<
function buildTree(node) {
if (!node) return [];
const h3 = node.querySelector(':scope > h3') || node.querySelector(':scope > a');
let result = {
title: h3.innerText,
children: []
};
const array = [...node.querySelectorAll(':scope > dl > dt')];
result.children = array.map(el => buildTree(el));
return result;
}