将 HTML 映射到 JSON

IT技术 javascript html json
2021-02-09 09:20:34

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

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

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

像这样进入一个json对象:

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

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

5个回答

我刚刚编写了这个功能,可以满足您的需求;尝试一下,如果它不适合您,请告诉我:

// 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+ 等)中包含的对象;如果您需要支持旧浏览器,您可以包含json2.js

它可以在任何一个DOM元素或string含有有效的XHTML作为一个参数(我相信,我不知道是否DOMParser()会在某些情况下噎住它设置为"text/xml"还是它只是不提供错误处理。不幸的是"text/html"有差浏览器支持)。

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

尽管我真的想让它使用字符串,而不是从 dom 中读取...
2021-03-18 09:20:34
我认为使用 EHTML 中的 e-json 更好:github.com/Guseyn/EHTML
2021-03-23 09:20:34
感谢@George Reith 提供的代码,对工作帮助很大。问题 - 是否有理由不使用nodeList[i-1]递归?我尝试实施它但没有成功
2021-03-29 09:20:34
github.com/raul1991/diver.js试试这个。看起来很简单,效果很好。
2021-04-12 09:20:34

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

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

// data Elem = Elem Node

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

// html2json :: Node -> JSONString
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– 这让我们更接近我们可能需要的

// data Elem = Elem Node | TextElem Node

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

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

// fromNode :: Node -> Elem
const fromNode = e => {
  switch (e.nodeType) {
    case 3:  return TextElem(e)
    default: return Elem(e)
  }
}

// html2json :: Node -> JSONString
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 版本

在阅读 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>
我在想类型可能只是标签标题或内容类型,例如。'string'
2021-03-27 09:20:34

谢谢@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;
}

我有一个类似的问题,我想通过以下方式将 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)
}