Ответ 1
Если вы можете получить объект DOMDocument
, представляющий ваш HTML, вам просто нужно пройти его рекурсивно и построить структуру данных, которую вы хотите.
Преобразование HTML-документа в DOMDocument
должно быть таким же простым, как это:
function html_to_obj($html) {
$dom = new DOMDocument();
$dom->loadHTML($html);
return element_to_obj($dom->documentElement);
}
Тогда простой обход $dom->documentElement
, который дает описанную вами структуру, может выглядеть так:
function element_to_obj($element) {
$obj = array( "tag" => $element->tagName );
foreach ($element->attributes as $attribute) {
$obj[$attribute->name] = $attribute->value;
}
foreach ($element->childNodes as $subElement) {
if ($subElement->nodeType == XML_TEXT_NODE) {
$obj["html"] = $subElement->wholeText;
}
else {
$obj["children"][] = element_to_obj($subElement);
}
}
return $obj;
}
Тестовый пример
$html = <<<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<title> This is a test </title>
</head>
<body>
<h1> Is this working? </h1>
<ul>
<li> Yes </li>
<li> No </li>
</ul>
</body>
</html>
EOF;
header("Content-Type: text/plain");
echo json_encode(html_to_obj($html), JSON_PRETTY_PRINT);
Выход
{
"tag": "html",
"lang": "en",
"children": [
{
"tag": "head",
"children": [
{
"tag": "title",
"html": " This is a test "
}
]
},
{
"tag": "body",
"html": " \n ",
"children": [
{
"tag": "h1",
"html": " Is this working? "
},
{
"tag": "ul",
"children": [
{
"tag": "li",
"html": " Yes "
},
{
"tag": "li",
"html": " No "
}
],
"html": "\n "
}
]
}
]
}
Ответ на обновленный вопрос
Предлагаемое выше решение не работает с элементом <script>
, поскольку оно анализируется не как DOMText
, а как объект DOMCharacterData
. Это связано с тем, что расширение DOM в PHP основано на libxml2
,, которое анализирует ваш HTML как HTML 4.0, а в HTML 4.0 содержимое <script>
имеет тип CDATA
, а не #PCDATA
.
У вас есть два решения этой проблемы.
-
Простым, но не очень надежным решением было бы добавить флаг
LIBXML_NOCDATA
вDOMDocument::loadHTML
. (на самом деле я не уверен на 100% независимо от того, это работает для парсера HTML.) -
Более сложное, но, на мой взгляд, лучшее решение - добавить дополнительный тест, когда вы тестируете
$subElement->nodeType
до рекурсии. Рекурсивная функция будет выглядеть следующим образом:
function element_to_obj($element) {
echo $element->tagName, "\n";
$obj = array( "tag" => $element->tagName );
foreach ($element->attributes as $attribute) {
$obj[$attribute->name] = $attribute->value;
}
foreach ($element->childNodes as $subElement) {
if ($subElement->nodeType == XML_TEXT_NODE) {
$obj["html"] = $subElement->wholeText;
}
elseif ($subElement->nodeType == XML_CDATA_SECTION_NODE) {
$obj["html"] = $subElement->data;
}
else {
$obj["children"][] = element_to_obj($subElement);
}
}
return $obj;
}
Если вы нажмете на другую ошибку этого типа, первое, что вам нужно сделать, это проверить тип node $subElement
, потому что существует много других возможностей моя короткая функция примера не имела дело.
Кроме того, вы заметите, что libxml2
должен исправить ошибки в вашем HTML, чтобы иметь возможность создавать DOM для него. Вот почему элементы <html>
и <head>
появятся, даже если вы их не укажете. Вы можете избежать этого, используя флаг LIBXML_HTML_NOIMPLIED
.
Тестовая версия с script
$html = <<<EOF
<script type="text/javascript">
alert('hi');
</script>
EOF;
header("Content-Type: text/plain");
echo json_encode(html_to_obj($html), JSON_PRETTY_PRINT);
Выход
{
"tag": "html",
"children": [
{
"tag": "head",
"children": [
{
"tag": "script",
"type": "text\/javascript",
"html": "\n alert('hi');\n "
}
]
}
]
}