将 HTML 映射到 JSON

我试图将 HTML 映射到结构完整的 JSON 中。有没有类库可以做到这一点,或者我需要自己编写一个?我想如果没有 html2json 库,我可以从 xml2json 库开始。毕竟,html 只是 xml 的一个变体,对吗?

更新: 好的,我应该举个例子。我要做的是。解析一个 html 字符串:

<div>
<span>text</span>Text2
</div>

变成 json 对象,就像这样:

{
"type" : "div",
"content" : [
{
"type" : "span",
"content" : [
"Text2"
]
},
"Text2"
]
}

注意 : 如果您没有注意到标记,我正在寻找 Javascript 中的解决方案

128867 次浏览

我得到了一些链接,而阅读 ExtJS 完整的框架本身就是 JSON。

Http://www.thomasfrank.se/xml_to_json.html

Http://camel.apache.org/xmljson.html

在线 XML 到 JSON 转换器: http://jsontoxml.utilities-online.info/

更新 顺便说一句,要添加 JSON,HTML 也需要类似这样的 type & content 标记,或者在进行 JSON 转换时需要使用一些 xslt 转换来添加这些元素

<?xml version="1.0" encoding="UTF-8" ?>
<type>div</type>
<content>
<type>span</type>
<content>Text2</content>
</content>
<content>Text2</content>

我刚刚编写了这个函数,它可以满足您的需要; 尝试一下,如果它不能正确工作,请让我知道:

// Test with an element.
var initElement = document.getElementsByTagName("html")[0];
var json = mapDOM(initElement, true);
console.log(json);


// Test with a string.
initElement = "<div><span>text</span>Text2</div>";
json = mapDOM(initElement, true);
console.log(json);


function mapDOM(element, json) {
var treeObject = {};
    

// If string convert to document Node
if (typeof element === "string") {
if (window.DOMParser) {
parser = new DOMParser();
docNode = parser.parseFromString(element,"text/xml");
} else { // Microsoft strikes again
docNode = new ActiveXObject("Microsoft.XMLDOM");
docNode.async = false;
docNode.loadXML(element);
}
element = docNode.firstChild;
}
    

//Recursively loop through DOM elements and assign properties to object
function treeHTML(element, object) {
object["type"] = element.nodeName;
var nodeList = element.childNodes;
if (nodeList != null) {
if (nodeList.length) {
object["content"] = [];
for (var i = 0; i < nodeList.length; i++) {
if (nodeList[i].nodeType == 3) {
object["content"].push(nodeList[i].nodeValue);
} else {
object["content"].push({});
treeHTML(nodeList[i], object["content"][object["content"].length -1]);
}
}
}
}
if (element.attributes != null) {
if (element.attributes.length) {
object["attributes"] = {};
for (var i = 0; i < element.attributes.length; i++) {
object["attributes"][element.attributes[i].nodeName] = element.attributes[i].nodeValue;
}
}
}
}
treeHTML(element, treeObject);
    

return (json) ? JSON.stringify(treeObject) : treeObject;
}

工作示例: http://jsfiddle.net/JUSsf/(在 Chrome 中测试,我不能保证完全支持浏览器-你必须测试这个)。

它创建一个对象,该对象包含 HTML 页面的树结构,并使用大多数现代浏览器(IE8 + ,Firefox 3 +)中包含的 JSON.stringify()。等) ; 如果你需要支持较老的浏览器,你可以包括 Json2.js

它可以使用一个 DOM 元素或者一个包含有效 XHTML 的 string作为参数(我相信,我不确定 DOMParser()在设置为 "text/xml"时在某些情况下是否会卡住,或者它只是不提供错误处理。不幸的是,"text/html"的浏览器支持很差)。

通过传递不同的 element值,可以很容易地更改此函数的范围。无论传递什么值,都将是 JSON 映射的根。

Htlm2json

表示复杂的 HTML 文档将是困难的,并且充满了角落情况,但是我只是想分享一些技术来展示如何启动这种程序。这个答案的不同之处在于它使用了抽象化和 toJSON方法来递归地构建结果

下面,html2json是一个 很小函数,它接受一个 HTML 节点作为输入,并返回一个 JSON 字符串作为结果。请特别注意代码是如何相当平坦的,但是它仍然有足够的能力构建一个深度嵌套的树结构——所有这些几乎都是零复杂性的

const Elem = e => ({
tagName:
e.tagName,
textContent:
e.textContent,
attributes:
Array.from(e.attributes, ({name, value}) => [name, value]),
children:
Array.from(e.children, Elem)
})


const html2json = e =>
JSON.stringify(Elem(e), null, '  ')
  

console.log(html2json(document.querySelector('main')))
<main>
<h1 class="mainHeading">Some heading</h1>
<ul id="menu">
<li><a href="/a">a</a></li>
<li><a href="/b">b</a></li>
<li><a href="/c">c</a></li>
</ul>
<p>some text</p>
</main>

在前面的示例中,textContent有点被破坏了。为了解决这个问题,我们引入了另一个数据构造函数 TextElem。我们将不得不映射到 childNodes(而不是 children) ,并选择返回基于 e.nodeType的正确数据类型-这使我们更接近我们可能需要的

const TextElem = e => ({
type:
'TextElem',
textContent:
e.textContent
})


const Elem = e => ({
type:
'Elem',
tagName:
e.tagName,
attributes:
Array.from(e.attributes, ({name, value}) => [name, value]),
children:
Array.from(e.childNodes, fromNode)
})


const fromNode = e => {
switch (e?.nodeType) {
case 1: return Elem(e)
case 3: return TextElem(e)
default: throw Error(`unsupported nodeType: ${e.nodeType}`)
}
}


const html2json = e =>
JSON.stringify(Elem(e), null, '  ')
  

console.log(html2json(document.querySelector('main')))
<main>
<h1 class="mainHeading">Some heading</h1>
<ul id="menu">
<li><a href="/a">a</a></li>
<li><a href="/b">b</a></li>
<li><a href="/c">c</a></li>
</ul>
<p>some text</p>
</main>

不管怎样,这只是问题的两个迭代。当然,您必须处理出现这些问题的边缘情况,但是这种方法的优点是,它为您提供了很大的灵活性,可以按照您希望的方式在 JSON-还有中编码 HTML,而不会引入太多复杂性

根据我的经验,您可以继续使用这种技术进行迭代,并取得非常好的结果。如果任何人对这个答案感兴趣,并希望我进一步阐述,请让我知道 ^ _ ^

相关阅读: 使用 JavaScript 的递归方法: 构建自己的 JSON.stringify 版本


Json2html

上面我们从 HTML 到 JSON,现在我们可以从 JSON 到 HTML。当我们可以在两种数据类型之间进行转换而不丢失数据时,这称为 同构。我们在这里要做的就是写出上面每个函数的逆函数-

const HtmlNode = (tagName, attributes = [], children = []) => {
const e = document.createElement(tagName)
for (const [k, v] of attributes) e.setAttribute(k, v)
for (const child of children) e.appendChild(toNode(child))
return e
}


const TextNode = (text) => {
return document.createTextNode(text)
}
  

const toNode = t => {
switch (t?.type) {
case "Elem": return HtmlNode(t.tagName, t.attributes, t.children)
case "TextElem": return TextNode(t.textContent)
default: throw Error("unsupported type: " + t.type)
}
}


const json2html = json =>
toNode(JSON.parse(json))


const parsedJson =
{"type":"Elem","tagName":"MAIN","attributes":[],"children":[{"type":"TextElem","textContent":"\n  "},{"type":"Elem","tagName":"H1","attributes":[["class","mainHeading"]],"children":[{"type":"TextElem","textContent":"Some heading"}]},{"type":"TextElem","textContent":"\n  "},{"type":"Elem","tagName":"UL","attributes":[["id","menu"]],"children":[{"type":"TextElem","textContent":"\n    "},{"type":"Elem","tagName":"LI","attributes":[],"children":[{"type":"Elem","tagName":"A","attributes":[["href","/a"]],"children":[{"type":"TextElem","textContent":"a"}]}]},{"type":"TextElem","textContent":"\n    "},{"type":"Elem","tagName":"LI","attributes":[],"children":[{"type":"Elem","tagName":"A","attributes":[["href","/b"]],"children":[{"type":"TextElem","textContent":"b"}]}]},{"type":"TextElem","textContent":"\n    "},{"type":"Elem","tagName":"LI","attributes":[],"children":[{"type":"Elem","tagName":"A","attributes":[["href","/c"]],"children":[{"type":"TextElem","textContent":"c"}]}]},{"type":"TextElem","textContent":"\n  "}]},{"type":"TextElem","textContent":"\n  "},{"type":"Elem","tagName":"P","attributes":[],"children":[{"type":"TextElem","textContent":"some text"}]},{"type":"TextElem","textContent":"\n"}]}


document.body.appendChild(toNode(parsedJson))

谢谢@Gorge Reith。通过@George Reith 提供的解决方案,这里有一个函数可以进一步(1)分离单独的“ href”链接(因为它们可能有用) ,(2)使用属性作为键(因为属性更具描述性) ,(3)在 Node.js 中使用“ jsdom”包而不需要 Chrome 就可以使用它:

const jsdom = require('jsdom') // npm install jsdom provides in-built Window.js without needing Chrome




// Function to map HTML DOM attributes to inner text and hrefs
function mapDOM(html_string, json) {
treeObject = {}


// IMPT: use jsdom because of in-built Window.js
// DOMParser() does not provide client-side window for element access if coding in Nodejs
dom = new jsdom.JSDOM(html_string)
document = dom.window.document
element = document.firstChild


// Recursively loop through DOM elements and assign attributes to inner text object
// Why attributes instead of elements? 1. attributes more descriptive, 2. usually important and lesser
function treeHTML(element, object) {
var nodeList = element.childNodes;
if (nodeList != null) {
if (nodeList.length) {
object[element.nodeName] = []  // IMPT: empty [] array for non-text recursivable elements (see below)
for (var i = 0; i < nodeList.length; i++) {
// if final text
if (nodeList[i].nodeType == 3) {
if (element.attributes != null) {
for (var j = 0; j < element.attributes.length; j++) {
if (element.attributes[j].nodeValue !== '' &&
nodeList[i].nodeValue !== '') {
if (element.attributes[j].name === 'href') { // separate href
object[element.attributes[j].name] = element.attributes[j].nodeValue;
} else {
object[element.attributes[j].nodeValue] = nodeList[i].nodeValue;
}


}
}
}
// else if non-text then recurse on recursivable elements
} else {
object[element.nodeName].push({}); // if non-text push {} into empty [] array
treeHTML(nodeList[i], object[element.nodeName][object[element.nodeName].length -1]);
}
}
}
}
}
treeHTML(element, treeObject);


return (json) ? JSON.stringify(treeObject) : treeObject;
}

我也遇到过类似的问题,我想用以下方式将 HTML 表示为 JSON:

  • 对于 HTML 文本节点,使用 string
  • 对于 HTML 元素,使用具有以下特性的数组:
    • 元素的(标记)名称
    • 一个对象,将属性键映射到属性值
    • 子节点的(内联)列表

例如:

<div>
<span>text</span>Text2
</div>

变成了

[
'div',
{},
['span', {}, 'text'],
'Text2'
]

我编写了一个函数来处理将 DOM Element 转换成这种 JS 结构的问题。你可以在这个答案的末尾找到这个函数。该函数是用 Typecript 编写的。您可以使用 打字游乐场将其转换为清理 JavaScript。


此外,如果需要将 html 字符串解析为 DOM,则将其赋值给 .innerHtml:

let element = document.createElement('div')
element.innerHtml = htmlString

此外,这是常识,但如果您需要 JSON 字符串输出,请使用 JSON.stringify


/**
* A NodeDescriptor stands for either an (HTML) Element, or for a text node
*/
export type NodeDescriptor = ElementDescriptor | string


/**
* Array representing an HTML Element. It consists of:
*
* - The (tag) name of the element
* - An object, mapping attribute keys to attribute values
* - The (inlined) list of children nodes
*/
export type ElementDescriptor = [
string,
Record<string, string>,
...NodeDescriptor[]
]


export let htmlToJs = (element: Element, trim = true): ElementDescriptor => {
let convertElement = (element: Element): ElementDescriptor => {
let attributeObject: Record<string, string> = {}
for (let { name, value } of element.attributes) {
attributeObject[name] = value
}


let childArray: NodeDescriptor[] = []
for (let node of element.childNodes) {
let converter = htmlToJsDispatch[node.nodeType]
if (converter) {
let descriptor = converter(node as any)
let skip = false


if (trim && typeof descriptor === 'string') {
descriptor = descriptor.trim()
if (descriptor === '') skip = true
}


if (!skip) childArray.push(descriptor)
}
}


return [element.tagName.toLowerCase(), attributeObject, ...childArray]
}


let htmlToJsDispatch = {
[element.ELEMENT_NODE]: convertElement,
[element.TEXT_NODE]: (node: Text): string => node.data,
}


return convertElement(element)
}