Несколько перестановок ваших предложенных функций работают, но только если вы указываете аргумент character.only
как TRUE
. Быстрый пример:
lapply(x, require, character.only = TRUE)
Если вы можете получить объект 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;
}
Если вы нажмете на другую ошибку этого типа, первое, что вам нужно сделать, это проверить тип узла $subElement
, поскольку существует много других возможностей моя короткая функция примера не имела дело.
Кроме того, вы заметите, что libxml2
исправляет ошибки в вашем HTML, чтобы иметь возможность создавать DOM для этого. Вот почему элементы <html>
и <head>
появятся, даже если вы их не укажете. Вы можете избежать этого, используя флаг LIBXML_HTML_NOIMPLIED
.
Тестовый сценарий со сценарием
$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 "
}
]
}
]
}