73

我正在尝试将 HTML 映射到结构完整的 JSON 中。是否有任何图书馆可以做到这一点,或者我需要自己编写?我想如果那里没有 html2json 库,我可以从 xml2json 库开始。毕竟,html 只是 xml 的变体,对吧?

更新:好的,我应该举个例子。我正在尝试做的是以下内容。解析一串html:

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

像这样进入一个json对象:

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

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

4

5 回答 5

86

我刚刚写了这个函数来做你想要的;试试看,如果它不适合您,请告诉我:

// 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 页面的树结构,然后使用JSON.stringify()它包含在大多数现代浏览器(IE8+、Firefox 3+ .etc)中;如果您需要支持较旧的浏览器,您可以包含json2.js

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

您可以通过传递不同的值来轻松更改此函数的范围element。您传递的任何值都将成为 JSON 映射的根。

于 2012-10-20T00:19:46.130 回答
15

html2json

表示复杂的 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))

于 2017-05-31T19:58:39.433 回答
1

在阅读 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 也需要像这样在其中包含类型和内容标签,或者您需要在进行 JSON 转换时使用一些 xslt 转换来添加这些元素

<?xml version="1.0" encoding="UTF-8" ?>
<type>div</type>
<content>
    <type>span</type>
    <content>Text2</content>
</content>
<content>Text2</content>
于 2012-10-19T19:19:06.060 回答
1

我有一个类似的问题,我想通过以下方式将 HTML 表示为 JSON:

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

例子:

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

变成

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

我编写了一个函数来处理将 DOM 元素转换为这种 JS 结构。您可以在此答案的末尾找到此功能。该函数是用 Typescript 编写的。您可以使用Typescript Playground将其转换为干净的 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)
}
于 2020-05-26T00:13:47.283 回答
0

谢谢@Gorge Reith。解决@George Reith 提供的解决方案,这里有一个功能,它进一步(1)分离出各个“hrefs”链接(因为它们可能有用),(2)使用属性作为键(因为属性更具描述性), (3) 通过使用 'jsdom' 包,它可以在 Node.js 中使用,而无需 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;
}
于 2020-02-12T02:51:57.640 回答