次の入力が与えられます:
<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>
上記の入力に基づいてJSONオブジェクトを作成したいと思います。
{
"title": "Title A",
"children": [
{
"title": "Title A- A",
"children": [
{"title": "Item"},
{"title": "Item"}
]
},
{"title": "Item"},
{"title": "Item"},
{"title": "Item"},
{"title": "Item"},
{
"title": "Title B- A",
"children": [
{"title": "Item"},
{"title": "Item"}
]
},
{"title": "Item"}
]
}
これが私がこれまでに試したことです:
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": []
}
]
}
一部のデータがないようですが、何が欠けているのでしょうか。
HTMLを修正
まず、あなたが誤用していることに注意しdl
ます。MDNドキュメントから-
HTML
<dl>
要素は説明リストを表します。要素は、用語のグループ(<dt>
要素を使用して指定)と説明(<dd>
要素によって提供される)のリストを囲みます...
ここでは、どのような正しい使い方のだdl
、dt
とdd
のようになります-
<dl>
<dt>Title 1</dt>
<dd>
<dl>
<dt>Title 1.1</dt>
<dd><a href="#">Item 1.1.1</a></dd>
<dd><a href="#">Item 1.1.2</a></dd>
</dl>
</dd>
<dd><a href="#">Item 1.2</a></dd>
<dd><a href="#">Item 1.3</a></dd>
<dd><a href="#">Item 1.4</a></dd>
<dd><a href="#">Item 1.5</a></dd>
<dd>
<dl>
<dt>Title 1.6</dt>
<dd><a href="#">Item 1.6.1</a></dd>
<dd><a href="#">Item 1.6.2</a></dd>
</dl>
</dd>
<dd><a href="#">Item 1.7</a></dd>
</dl>
出力の予想される形状と一致することに注意してください-
{
"title": "Title 1",
"children": [
{
"title": "Title 1.1",
"children": [
{"title": "Item 1.1.1"},
{"title": "Item 1.1.2"}
]
},
{"title": "Item 1.2"},
{"title": "Item 1.3"},
{"title": "Item 1.4"},
{"title": "Item 1.5"},
{
"title": "Title 1.6",
"children": [
{"title": "Item 1.6.1"},
{"title": "Item 1.6.2"}
]
},
{"title": "Item 1.7"}
]
}
fromHtml
上記のように入力HTMLを変更する意思がない(または変更できない)場合は、Scottのすばらしい回答を参照してください。提案されたhtmlのプログラムを書くために、私はそれを2つの部分に分けます。まずfromHtml
、単純な再帰形式で記述します-
function fromHtml (e)
{ switch (e?.tagName)
{ case "DL":
return Array.from(e.childNodes, fromHtml).flat()
case "DD":
return [ Array.from(e.childNodes, fromHtml).flat() ]
case "DT":
case "A":
return e.textContent
default:
return []
}
}
fromHtml(document.querySelector('dl'))
これは私たちにこの中間フォーマットを与えます-
[
"Title 1",
[
"Title 1.1",
[ "Item 1.1.1" ],
[ "Item 1.1.2" ]
],
[ "Item 1.2" ],
[ "Item 1.3" ],
[ "Item 1.4" ],
[ "Item 1.5" ],
[
"Title 1.6",
[ "Item 1.6.1" ],
[ "Item 1.6.2" ]
],
[ "Item 1.7" ]
]
applyLabels
その後、必要なラベルとラベルapplyLabels
を追加する別の関数を作成します-title
children
const applyLabels = ([ title, ...children ]) =>
children.length
? { title, children: children.map(applyLabels) }
: { title }
const result =
applyLabels(fromHtml(document.querySelector('dl')))
{
"title": "Title 1",
"children": [
{
"title": "Title 1.1",
"children": [
{"title": "Item 1.1.1"},
{"title": "Item 1.1.2"}
]
},
{"title": "Item 1.2"},
{"title": "Item 1.3"},
{"title": "Item 1.4"},
{"title": "Item 1.5"},
{
"title": "Title 1.6",
"children": [
{"title": "Item 1.6.1"},
{"title": "Item 1.6.2"}
]
},
{"title": "Item 1.7"}
]
}
出力内のすべてのノードが均一な形状になることを保証する1つの最終的な変更を提案するかもしれません{ title, children }
。この場合にはので、それは注目に変更する価値があるapplyLabels
の書き込みに簡単ですし、それが良い振る舞い-
const applyLabels = ([ title, ...children ]) =>
({ title, children: children.map(applyLabels) })
はい、これは最も深い子孫が空のchildren: []
プロパティを持つことを意味しますが、特定のプロパティをnullチェックする必要がないため、データの消費がはるかに簡単になります。
デモ
以下のスニペットを展開fromHtml
してapplyLabels
、ご使用のブラウザでの結果を確認してください-
function fromHtml (e)
{ switch (e?.tagName)
{ case "DL":
return Array.from(e.childNodes, fromHtml).flat()
case "DD":
return [ Array.from(e.childNodes, fromHtml).flat() ]
case "DT":
case "A":
return e.textContent
default:
return []
}
}
const applyLabels = ([ title, ...children ]) =>
children.length
? { title, children: children.map(applyLabels) }
: { title }
const result =
applyLabels(fromHtml(document.querySelector('dl')))
console.log(result)
<dl>
<dt>Title 1</dt>
<dd>
<dl>
<dt>Title 1.1</dt>
<dd><a href="#">Item 1.1.1</a></dd>
<dd><a href="#">Item 1.1.2</a></dd>
</dl>
</dd>
<dd><a href="#">Item 1.2</a></dd>
<dd><a href="#">Item 1.3</a></dd>
<dd><a href="#">Item 1.4</a></dd>
<dd><a href="#">Item 1.5</a></dd>
<dd>
<dl>
<dt>Title 1.6</dt>
<dd><a href="#">Item 1.6.1</a></dd>
<dd><a href="#">Item 1.6.2</a></dd>
</dl>
</dd>
<dd><a href="#">Item 1.7</a></dd>
</dl>
備考
再帰とデータ変換のトピックについて何百もの回答を書きましたが、それでも私が本質的な方法で使用したのはこれが初めてだと思います.flat
。このQ&Aにはユースケースがあると思いましたが、スコットのコメントは私からそれを受け取りました!domNode.childNodes
は真の配列でArray.prototype.flatMap
はないため、使用できないため、この回答は異なります。興味深い問題をありがとう。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加