PHP Jquery:从给定的 url 将 HTML 转换为 JSON 并创建 html 元素的树视图
Posted
技术标签:
【中文标题】PHP Jquery:从给定的 url 将 HTML 转换为 JSON 并创建 html 元素的树视图【英文标题】:PHP Jquery:Convert HTML to JSON from given url and create a tree view of html elements 【发布时间】:2015-05-28 15:01:46 【问题描述】:基本上我有一个文本框,我将在其中输入 URL 并单击“确定按钮”,它将在页面左侧显示 html 预览;右侧将有一个在 HTML 中用作附加图像的 HTML 标记(正文、标题、div、span 等)的树形视图。预期的 JSON 结果应该作为这个问题的结尾。我无法遍历 JSON 并创建树。我尝试了以下方法:
HTML 和 JS 代码:
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ABC</title>
<link rel="stylesheet" type="text/css" href="css/main.css" />
</head>
<body>
<div id="wrapper">
<header>
<h1 class="logo"><img src="images/logo.png" title="" /></h1>
</header>
<div id="container">
<div class="search-box">
<input type="text" id="url" value="" class="txt-box" />
<input type="button" value="OK" class="btn-search" />
</div>
<div class="inner-wrap">
<div class="left-wrap" id="preview-sec">
</div>
<div class="right-wrap" id="tree-sec">
</div>
</div>
</div>
</div>
<script type="text/javascript" language="javascript" src="js/jquery-1.11.1.js"></script><!-- Jquery plugin -->
<script>
var counter = 0;
$(document).ready(function()
$('.btn-search').click(function()
if ($('#url').val() != '')
$.get(
'http://localhost/test/getHTML.php', url:$('#url').val(),
function(response)
$('#preview-sec').html(response);
,'html');
$.getJSON('http://localhost/test/results.json', function(json)
traverse(json,0);
);
);
);
function traverse(obj,id)
if (typeof(obj)=="object")
if (id == 0)
$('#tree-sec').append('<ul></ul>');
else
$(id).append('<ul></ul>');
$.each(obj, function(i,val)
if (i != 'attributes' && i != 'value')
counter += 1;
var li_populate = "<li id="+i+"-"+counter+">"+i+"</li>";
if (id == 0)
$('#tree-sec ul').append(li_populate);
else
$(id).find('ul').append(li_populate);
traverse(val,"#"+i+"-"+counter);
)
</script>
</body>
</html>
PHP 代码:
<?php
$url = $_GET['url'];
$html = file_get_contents($url);
function html_to_obj($html)
$dom = new DOMDocument();
$dom->loadHTML($html);
return element_to_obj($dom->documentElement);
function element_to_obj($element)
//print_r($element);
$obj = array();
$attr = array();
$arr = array();
$name = $element->tagName;
foreach ($element->attributes as $attribute)
$attr[$attribute->name] = $attribute->value;
if ($attribute->name == 'id')
$name .= '#'.$attribute->value;
if (!empty($attr))
$arr["attributes"] = $attr;
if ($element->nodeValue != '')
$arr["value"] = $element->nodeValue;
foreach ($element->childNodes as $subElement)
if ($subElement->nodeType == XML_TEXT_NODE)
elseif ($subElement->nodeType == XML_CDATA_SECTION_NODE)
else
$arr["child_nodes"][] = element_to_obj($subElement);
$obj[$name] = $arr;
return $obj;
$json = json_encode(html_to_obj($html));
$fp = fopen('results.json', 'w');
fwrite($fp,$json);
fclose($fp);
echo $html;exit();
?>
JSON 树输出:
JSON 结果:
"html":
"attributes":
"lang": "en"
,
"value": "Test Development Test\r\n *\r\n box-sizing:border-box;\r\n \r\n body \r\n margin:0;\r\n font-family: sans-serif;\r\n color: #999;\r\n \r\n a, a:visited \r\n text-decoration:none;\r\n \r\n .movie-list .movie\r\n width:250px;\r\n float:left;\r\n margin-right:25px;\r\n \r\n .movie-list .movie img\r\n width:100%;\r\n \r\n .movie-list .movie a.title\r\n text-decoration:none;\r\n color:#999;\r\n font-weight:bold;\r\n font-size:18px;\r\n line-height:25px;\r\n \r\n .movie-list .movie .synopsis\r\n font-size:14px;\r\n line-height:20px;\r\n \r\n",
"child_nodes":
"head":
"child_nodes":
"meta":
"attributes":
"name": "description",
"content": "A ast of animated movies"
,
"title":
"value": "Test Development Test"
,
"style":
"attributes":
"type": "text/css"
,
"value": "\r\n *\r\n box-sizing:border-box;\r\n \r\n body \r\n margin:0;\r\n font-family: sans-serif;\r\n color: #999;\r\n \r\n a, a:visited \r\n text-decoration:none;\r\n \r\n .movie-list .movie\r\n width:250px;\r\n float:left;\r\n margin-right:25px;\r\n \r\n .movie-list .movie img\r\n width:100%;\r\n \r\n .movie-list .movie a.title\r\n text-decoration:none;\r\n color:#999;\r\n font-weight:bold;\r\n font-size:18px;\r\n line-height:25px;\r\n \r\n .movie-list .movie .synopsis\r\n font-size:14px;\r\n line-height:20px;\r\n \r\n"
,
"body":
"child_nodes":
"h1":
"value": "List of animated movies"
,
"div":
"attributes":
"class": "movie-list"
,
"child_nodes":
"div#bh_6":
"attributes":
"class": "movie",
"id": "bh_6",
"data-year": "2014"
,
"child_nodes":
"img":
"attributes":
"src": "http://ia.media-imdb.com/images/M/MV5BMjI4MTIzODU2NV5BMl5BanBnXkFtZTgwMjE0NDAwMjE@._V1_SY317_CR0,0,214,317_AL_.jpg"
,
"a":
"attributes":
"class": "title",
"href": "http://www.imdb.com/title/tt2245084/"
,
"value": "Big Hero 6"
,
"div":
"attributes":
"class": "synopsis"
,
"value": "The special bond that develops between plus-sized inflatable robot Baymax, and prodigy Hiro Hamada, who team up with a group of friends to form a band of high-tech heroes."
,
"div#tlm":
"attributes":
"class": "movie",
"id": "tlm",
"data-year": "2014"
,
"child_nodes":
"img":
"attributes":
"src": "http://ia.media-imdb.com/images/M/MV5BMTg4MDk1ODExN15BMl5BanBnXkFtZTgwNzIyNjg3MDE@._V1_SX214_AL_.jpg"
,
"a":
"attributes":
"class": "title",
"href": "http://www.imdb.com/title/tt1490017/"
,
"value": "The Lego Movie"
,
"div":
"attributes":
"class": "synopsis"
,
"value": "An ordinary Lego construction worker, thought to be the prophesied 'Special', is recruited to join a quest to stop an evil tyrant from gluing the Lego universe into eternal stasis."
,
"div#httyd":
"attributes":
"class": "movie",
"id": "httyd",
"data-year": "2010"
,
"child_nodes":
"img":
"attributes":
"src": "http://ia.media-imdb.com/images/M/MV5BMjA5NDQyMjc2NF5BMl5BanBnXkFtZTcwMjg5ODcyMw@@._V1_SX214_AL_.jpg"
,
"a":
"attributes":
"class": "title",
"href": "http://www.imdb.com/title/tt0892769/"
,
"value": "How to Train Your Dragon"
,
"div":
"attributes":
"class": "synopsis"
,
"value": "A hapless young Viking who aspires to hunt dragons becomes the unlikely friend of a young dragon himself, and learns there may be more to the creatures than he assumed."
,
"div#up":
"attributes":
"class": "movie",
"id": "up",
"data-year": "2009"
,
"child_nodes":
"img":
"attributes":
"src": "http://ia.media-imdb.com/images/M/MV5BMTk3NDE2NzI4NF5BMl5BanBnXkFtZTgwNzE1MzEyMTE@._V1_SX214_AL_.jpg"
,
"a":
"attributes":
"class": "title",
"href": "http://www.imdb.com/title/tt1049413/"
,
"value": "Up"
,
"div":
"attributes":
"class": "synopsis"
,
"value": "By tying thousands of balloons to his home, 78-year-old Carl sets out to fulfill his lifelong dream to see the wilds of South America. Russell, a wilderness explorer 70 years younger, inadvertently becomes a stowaway."
,
"div#mi":
"attributes":
"class": "movie",
"id": "mi",
"data-year": "2001"
,
"child_nodes":
"img":
"attributes":
"src": "http://ia.media-imdb.com/images/M/MV5BMTY1NTI0ODUyOF5BMl5BanBnXkFtZTgwNTEyNjQ0MDE@._V1_SX214_AL_.jpg"
,
"a":
"attributes":
"class": "title",
"href": "http://www.imdb.com/title/tt0198781/"
,
"value": "Monsters, Inc."
,
"div":
"attributes":
"class": "synopsis"
,
"value": "Monsters generate their city's power by scaring children, but they are terribly afraid themselves of being contaminated by children, so when one enters Monstropolis, top scarer Sulley finds his world disrupted."
【问题讨论】:
您实际上是在尝试创建树的图像,还是在尝试创建类似于上图中的树视图? 你试过 PHP Simple HTML Dom 库吗? simplehtmldom.sourceforge.net/manual.htm - 你想了解体内的元素吗?文档 ?具有类 / id 的元素? 你能发布json结果吗?这是回答您的问题所需要的。其他部分是偶然的。 我添加了预期的 JSON 结果并稍微改进了问题以便更多理解。 我不需要树的图像,它是一个 HTML 可点击的树,您可以在其中像手风琴一样打开和关闭树枝。 【参考方案1】:根据您的问题,您遍历返回的 json 对象并创建树的部分存在问题。在您的代码中,用于遍历 json 数据的递归函数在生成 ul
代码时存在一些小问题。返回对象的结构使它有点挑战。
我能够稍微修改您的html/javascript
代码(无需过多更改)以打印出树。相关代码如下:
CSS:
div#tree-sec ul ul
margin-left: 25px;
div#tree-sec ul li
color: #666;
div#tree-sec ul a
color: #111;
text-decoration: underline;
cursor: pointer;
div#tree-sec ul a:hover
text-decoration: none;
div#tree-sec ul.collapsible
/* Custom parent styles here... */
/* Such as a folder icon or 'plus' sign */
HTML 和 JS:
...
...
<div class="inner-wrap">
<div class="left-wrap" id="preview-sec">
<iframe id="preview"></iframe>
</div>
<div class="right-wrap" id="tree-sec">
<ul id="treehtml1"></ul>
</div>
</div>
....
....
<script type="text/javascript">
var counter = 0;
$(document).ready(function()
$('.btn-search').click(function()
if ($('#url').val() != '')
$.get('http://localhost/test/getHTML.php', url:$('#url').val(), function(response)
$('#preview-sec').html(response);
,'html');
$.getJSON('http://localhost/test/results.json', function(json)
if(typeof(json) == "object")
traverse(json,'html',1);
makeCollapsible();
);
);
);
function traverse(obj, element, counter)
for (var i in obj)
$("#tree"+element+counter).append("<li id='"+i+counter+"'>"+i+"</li>"); // Add element to the tree
if(obj[i].hasOwnProperty('child_nodes'))
$("#"+i+counter).append("<ul id='tree"+i+(counter+1)+"'></ul>"); // If there are children, add a parent ul and pass the name to subsequent recursive calls for each child
for(var j in obj[i].child_nodes)
traverse(obj[i].child_nodes[j], i, counter + 1); // Recursive call to add child
function makeCollapsible()
$('ul.parent').each(function(i)
var parent_li = $(this).parent('li');
parent_li.addClass('collapsible'); //Use this selector to style your parent items...
// Temporarily remove the list from the
// parent list item, wrap the remaining
// text in an anchor, then reattach it.
var sub_ul = $(this).remove();
parent_li.wrapInner('<a/>').children('a').click(function()
// Toggle the children...
sub_ul.toggle();
);
parent_li.append(sub_ul);
);
// Hide all lists except the outermost.
$('ul ul').hide();
</script>
....
....
这应该提供一个正确嵌套的基于ul
的树。如果创建树的图像是一项硬性要求,最好的办法是正确设置生成的ul
代码片段的样式,在服务器上用它创建一个 html 页面,然后使用服务器端工具,例如wkhtmltoimage from the wkhtmltopdf package可用于将 html 文档呈现为图像。
另外,我想提到的另一件事是,与其将检索到的 html 加载到 div 中,我建议您使用 iframe
,因为这样检索到的 html 不会干扰您当前的页面。在上面的示例中,我在预览div
中添加了一个iframe
。在这种情况下,您可以使用 php 仅输出 json
数据并设置 iframe
以预览 url 就像将 url 分配为 iframe
的 src
属性一样简单。像这样:$("#preview").prop("src", $("#url").val())
。
编辑:
更新了代码并进行了修复。还添加了一个新的 js 函数 makeCollapsible()
以根据 OP 的评论将 ul
追溯转换为可点击、可折叠的树结构。还添加了相关的CSS
样式来设置树结构的样式。对我来说,树现在看起来如下图所示:
【讨论】:
【参考方案2】:看看这个由 Jack 写的Library。
https://github.com/Jxck/html2json
希望对你有帮助。
【讨论】:
【参考方案3】:附录:这是一个很长的答案,但它针对您提供的代码 sn-ps 解决了具体问题和解决方案。我希望您和其他人会发现值得花时间进行比较。 :)
首先,修改你的 PHP 以使 JSON 更清晰
在解析 DOM 时,我建议使用 array_merge()
将返回对象中的元素名称设置为 $arr['child_nodes']
中的关联键,而不是将它们作为索引项推送到数组中。为此,必须首先将$arr['child_nodes']
定义为一个数组。稍后,如果没有项目被合并到其中,您只需在 $arr
被添加到主对象之前 unset
它。
这使得在构建树时无需在 javascript 中使用嵌套循环,从而使最终的 JSON 结果更易于解析。
我还建议在执行 foreach
循环之前插入对 ->length
的条件检查。当零长度元素进入循环时,您现有的代码会抛出“警告”消息。
最后,您可以选择简化处理节点类型的逻辑,将当前的 if, else if, else
语句替换为单个 if
检查 $subElement->nodeType === XML_ELEMENT_NODE
,我认为这就是您想要完成的。
<?php
$url = $_GET['url'];
$html = file_get_contents($url);
function html_to_obj($html)
$dom = new DOMDocument();
$dom->loadHTML($html);
return element_to_obj($dom->documentElement);
function element_to_obj($element)
$obj = $attr = $arr = array();
$name = $element->tagName;
if ($element->attributes->length)
foreach ($element->attributes as $attribute)
$attr[$attribute->name] = $attribute->value;
if ($attribute->name == 'id')
$name .= '#'.$attribute->value;
if (!empty($attr))
$arr["attributes"] = $attr;
if ($element->nodeValue != '')
$arr["value"] = $element->nodeValue;
if ($element->childNodes->length)
$arr["child_nodes"] = array();
foreach ($element->childNodes as $subElement)
if ($subElement->nodeType === XML_ELEMENT_NODE)
$arr["child_nodes"] = array_merge($arr["child_nodes"], element_to_obj($subElement));
if (!count($arr["child_nodes"]))
unset($arr["child_nodes"]);
$obj[$name] = $arr;
return $obj;
$json = json_encode(html_to_obj($html));
$fp = fopen('results.json', 'w');
fwrite($fp, $json);
fclose($fp);
?>
使用 iframe
插入一个空的 iframe,您将在其中加载您的目标网站。将其他网站的标记插入您的网站可能(并且很可能)会导致与您自己的代码发生冲突。
<div class="inner-wrap">
<div class="left-wrap" id="preview-sec">
<iframe src=""></iframe>
</div>
<div class="right-wrap" id="tree-sec">
</div>
</div>
简化遍历函数,重新排序异步调用
traverse
函数存在三个缺陷:
使用计数器动态创建 id,然后使用 jQuery 查找先前创建的元素,这些 id 在其上附加列表项,这会消耗性能并导致调试混乱。
李>.find()
的使用导致 jQuery 冗余进入递归调用并将多冗余子节点附加到树中。
因为它是单独异步调用的回调,所以它可以在对getHTML.php
的第一次异步调用完成之前执行。
将异步调用以获取 JSON 移动到第一个异步调用的回调函数中,以防止它从服务器获取不完整或旧的 JSON。
您还应该使用第一个回调来设置iframe
src
并清空#tree-sec
容器,以便后续操作不会附加多个树。您可以通过使用.replace()
而不是.empty()
后跟.append()
来完成同样的事情。
要构建树,我推荐以下更简单的方法,它将列表递归地构建为字符串,这样.append
方法只会被调用一次。对于较大的树,这将显着提高性能。
如果您愿意,您可以为该函数引入一个计数器和动态分配的 id,但为了更清楚地说明构建树不需要它们,我省略了它。
我还建议在进入递归调用之前检查子节点是否存在。执行此检查允许您仅传递子节点对象,由于对 PHP 脚本所做的更改产生了新的 JSON,该对象现在包含标签名称作为键,而不是索引键,并将元素作为子节点。如果我们没有简化 JSON,此时将需要第二个循环来检索每个元素。
您还会注意到包含aria-
属性和role
属性。如果您愿意,这将使您可以完全访问。
见:Using the WAI-ARIA aria-expanded state to mark expandable and collapsible regions (w3.org)
它还为您提供了一种方便且语义化的方式来控制 CSS 和切换状态,您可以在脚本底部添加的附加点击处理程序和本答案底部的 CSS 示例中看到这一点。
$(document).ready(function ()
function traverse(data, firstTime)
if (typeof data === 'object')
var ul = '<ul role="' + (firstTime ? 'tree' : 'group' ) + '">';
$.each(data, function (key, val)
if (key !== 'attributes' && key !== 'value')
if (val['child_nodes'])
ul += '<li aria-expanded="true" role="tree-item" tabindex="0">';
ul += key;
ul += traverse(val['child_nodes']);
ul += '</li>';
else
ul += '<li role="tree-item" tabindex="0">';
ul += key;
ul += '</li>';
);
ul += '</ul>';
return ul;
$('.btn-search').on('click', function ()
var url = $('#url').val();
if (url)
$.get(
'getHTML.php',
url: url
,
function ()
$('#preview-sec iframe').attr('src', url);
$('#tree-sec').empty();
$.get(
'results.json',
function (json)
$('#tree-sec').append(traverse(json, true));
,
'json'
);
,
'html'
);
);
$('#tree-sec').on('click', 'li[aria-expanded]', function (e)
e.stopPropagation();
$(this)
.attr('aria-expanded', function (i, attr)
return !(attr === 'true');
)
.children('ul')
.attr('aria-hidden', function (i, attr)
return !(attr === 'true');
)
.toggle();
);
);
奖励:在 CSS 中使用属性选择器
最后,如上所述,aria-
和 role
属性的存在为控制样式提供了一种语义方便的方法。
ul[role='tree']
margin-left: 1em;
padding-left: 0;
ul[role='tree'] li
cursor: default;
margin: 0;
padding: 0 0 0 20px;
font: normal 1em sans-serif;
color: #333;
ul[role='tree'] li[aria-expanded]
cursor: pointer;
font-weight: bold;
color: #111;
background: transparent 0 0 no-repeat url('images/arrow-sprite.png');
ul[role='tree'] li[aria-expanded="true"]
background-position: 0 0;
ul[role='tree'] li[aria-expanded="false"]
background-position: 0 20px;
【讨论】:
【参考方案4】:看看 XSLT 处理。用更少的代码工作就可以正常工作
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="json.xml"?>
<html>
<body>
<h1>title</h1>
<h2>title 2</h2>
<h3>title 3</h3>
<ul>
<li>t1</li>
<li>t2</li>
<li>t3</li>
</ul>
</body>
</html>
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template name="json" match="/">
<xsl:for-each select="*">
"<xsl:value-of select="local-name()"/>": "<xsl:if test="count(*)=0"><xsl:value-of select="text()"/></xsl:if>",
"attributes": <xsl:for-each select="@*"><xsl:if test="position()>1">,</xsl:if>"<xsl:value-of select="local-name()"/>": "<xsl:value-of select="text()"/>"</xsl:for-each>,<xsl:call-template name="json"/></xsl:for-each>
</xsl:template>
</xsl:stylesheet>
【讨论】:
【参考方案5】:为您的 HTML 标记构造多维 PHP 数组,然后将该数组作为 php 内置函数 json_encode($array) 的输入,这将返回树结构的 json 输出
【讨论】:
以上是关于PHP Jquery:从给定的 url 将 HTML 转换为 JSON 并创建 html 元素的树视图的主要内容,如果未能解决你的问题,请参考以下文章