我需要轉換一個帶有嵌套標簽的 HTML 字串,如下所示:
const strHTML = "<p>Hello World</p><p>I am a text with <strong>bold</strong> word</p><p><strong>I am bold text with nested <em>italic</em> Word.</strong></p>"
進入以下具有此結構的物件陣列:
const result = [{
text: "Hello World",
format: null
}, {
text: "I am a text with",
format: null
}, {
text: "bold",
format: ["strong"]
}, {
text: " word",
format: null
}, {
text: "I am a text with nested",
format: ["strong"]
}, {
text: "italic",
format: ["strong", "em"]
}, {
text: "Word.",
format: ["strong"]
}];
只要沒有嵌套標簽,我就使用 DOMParser() 管理轉換。我無法使用嵌套標簽運行它,就像在最后一段中一樣,所以我的整個段落都是粗體的,但是“斜體”這個詞應該是粗體和斜體。我不能讓它作為遞回運行。
任何幫助,將不勝感激。
所以到目前為止我寫的代碼是這樣的:
export interface Phrase {
text: string;
format: string | string[];
}
export class HTMLParser {
public parse(text: string): void {
const parser = new DOMParser();
const sourceDocument = parser.parseFromString(text, "text/html");
this.parseChildren(sourceDocument.body.childNodes);
// HERE SHOULD BE the result
console.log("RESULT of CONVERSION", this.phrasesProcessed);
}
public phrasesProcessed: Phrase[] = [];
private parseChildren(toParse: NodeListOf<ChildNode>) {
this.phrasesProcessed = [];
try {
Array.from(toParse)
.map(item => {
if (item.nodeType === Node.ELEMENT_NODE && item instanceof HTMLElement) {
return Array.from(item.childNodes).map(child => ({ text: child.textContent, format: (child.nodeType === Node.ELEMENT_NODE && child instanceof HTMLElement) ? child.tagName : null }));
} else {
return Array.from(item.childNodes).map(child => ({ text: child.textContent, format: null }));
}
})
.filter(line => line.length) // only non emtpy arrays
.map(element => ([...element, { text: "\n", format: null }])) // add linebreak after each P
.reduce((acc: (Phrase)[], val) => acc.concat(val), []) // flatten
.forEach(
element => {
// console.log("ELEMENT", element);
this.phrasesProcessed.push(element);
}
);
} catch (e) {
console.warn(e);
}
}
}
uj5u.com熱心網友回復:
您可以使用遞回。這對于生成器函式來說似乎是一個很好的例子。由于不清楚應該保留哪些標簽format(顯然,不是p),我將其作為配置提供:
const formatTags = new Set(["b", "big", "code", "del", "em", "i", "pre", "s", "small", "strike", "strong", "sub", "sup", "u"]);
function* iterLeafNodes(nodes, format=[]) {
for (let node of nodes) {
if (node.nodeType == 3) {
yield ({text: node.nodeValue, format: format.length ? [...format] : null});
} else {
const tag = node.tagName.toLowerCase();
yield* iterLeafNodes(node.childNodes,
formatTags.has(tag) ? format.concat(tag) : format);
}
}
}
// Example input
const strHTML = "<p>Hello World</p><p>I am a text with <strong>bold</strong> word</p><p><strong>I am bold text with nested <em>italic</em> Word.</strong></p>"
const nodes = new DOMParser().parseFromString(strHTML, 'text/html').body.childNodes;
let result = [...iterLeafNodes(nodes)];
console.log(result);
請注意,當文本分布在多個標簽上時,這仍會拆分文本,這些標簽被視為非格式標簽,例如span.
其次,我不相信擁有null一個可能的值format比一個空陣列更有用[],但無論如何,null在這種情況下,上面會產生。
特殊情況 - 插入\n
p在評論中,您要求在每個元素后插入換行符。
下面的代碼將生成那個額外的元素。在這里,我也用for[]代替:nullformat
const formatTags = new Set(["b", "big", "code", "del", "em", "i", "pre", "s", "small", "strike", "strong", "sub", "sup", "u"]);
function* iterLeafNodes(nodes, format=[]) {
for (let node of nodes) {
if (node.nodeType == 3) {
yield ({text: node.nodeValue, format: [...format]});
} else {
const tag = node.tagName.toLowerCase();
yield* iterLeafNodes(node.childNodes,
formatTags.has(tag) ? format.concat(tag) : format);
if (tag === "p") yield ({text: "\n", format: [...format]});
}
}
}
// Example input
const strHTML = "<p>Hello World</p><p>I am a text with <strong>bold</strong> word</p><p><strong>I am bold text with nested <em>italic</em> Word.</strong></p>"
const nodes = new DOMParser().parseFromString(strHTML, 'text/html').body.childNodes;
let result = [...iterLeafNodes(nodes)];
console.log(result);
uj5u.com熱心網友回復:
您可以遞回地回圈遍歷子節點并使用類似的陣列構造所需的陣列FORMAT_NODES。
const FORMAT_NODES = ["strong", "em"];
function getText(node, parents = [], res = []) {
if (node.nodeName === "#text") {
const text = node.textContent.trim();
if (text) {
const format = parents.filter((p) => FORMAT_NODES.includes(p));
res.push({ text, format: format.length ? format : null });
}
} else {
node.childNodes.forEach((node) =>
getText(node, parents.concat(node.nodeName.toLowerCase()), res)
);
}
return res;
}
const container = document.querySelector("#container");
const result = getText(container);
console.log(result);
<div id="container">
<p>Hello World</p>
<p>I am a text with <strong>bold</strong> word</p>
<p><strong>I am bold text with nested <em>italic</em> Word.</strong></p>
</div>
相關檔案:
- Node.childNodes
- Node.parentNode
- Array.prototype.concat
- Array.prototype.includes
uj5u.com熱心網友回復:
一個版本與此處發布的其他兩個版本并沒有什么不同,但職責細分不同。
const getTextNodes = (node, path = []) =>
node .nodeType === 3
? {text: node .nodeValue, path}
: [... node .childNodes] .flatMap ((child) => getTextNodes (child, [... path, node .tagName .toLowerCase()]))
const extract = (keep) => (html) =>
[...new DOMParser () .parseFromString (html, 'text/html') .body .childNodes]
.flatMap (node => getTextNodes (node))
.map (({text, path = []}) => ({text, format: [...new Set (path .filter (p => keep .includes (p)))]}))
const reformat = extract (["em", "strong"])
const strHTML = "<p>Hello World</p><p>I am a text with <strong>bold</strong> word</p><p><strong>I am bold text with nested <em>italic</em> Word.</strong></p>"
console .log (reformat (strHTML))
.as-console-wrapper {max-height: 100% !important; top: 0}
這通過一種中間格式,可能對其他目的有用:
[
{text: "Hello World", path: ["p"]},
{text: "I am a text with ", path: ["p"]},
{text: "bold", path: ["p", "strong"]},
{text: " word", path: ["p"]},
{text: "I am bold text with nested ", path: ["p", "strong"]},
{text: "italic", path: ["p", "strong", "em"]},
{text: " Word.", path: ["p", "strong"]}
]
雖然這看起來與您的最終格式相似,但它path包含了文本節點的整個標簽歷史記錄,并且可以用于各種目的。 getTextNodes從給定節點中提取此格式。因此 apath可能看起來像["div", "div", "div", "nav", "ol", "li", "a", "div", "div", "strong"],具有重復的元素和許多非格式化標簽。
最后的map呼叫extract只是將此路徑過濾到您配置的格式化標簽集合中。
雖然我們可以輕松地一次性完成此操作,但getTextNodes它本身就是一個有用的功能,我們可能會在系統的其他地方使用。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/487718.html
標籤:javascript html 递归 数据转换 解析器
上一篇:具有遞回的Python算術函式
