Подтвердить что ты не робот

PHP JQuery: конвертировать HTML в JSON из заданного url и создавать древовидные элементы html-элементов

В принципе у меня есть текстовое поле, где я буду вводить URL-адрес и нажимать кнопку "ОК", он покажет предварительный просмотр HTML на левой стороне страницы; и правая сторона будет иметь вид дерева HTML-тегов (body, header, div, span и т.д.), используемых в HTML в качестве прикрепленного изображения. Ожидаемый результат 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" alt="" 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:

enter image description here

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/[email protected]_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/[email protected]_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/[email protected]@._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/[email protected]_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/[email protected]_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 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."
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
4b9b3361

Ответ 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 из пакета wkhtmltopdf, который можно использовать для рендеринга html-документа в изображение.

Кроме того, еще одна вещь, которую я хотел бы упомянуть, заключается в том, что вместо того, чтобы загружать извлеченный html в div, я бы рекомендовал вам использовать iframe, так как извлеченный html не помешает вашей текущей странице. В моем примере выше я добавил iframe в предварительном просмотре div. В таком случае вы можете использовать php только для вывода данных json и установки iframe для предварительного просмотра URL-адреса будет так же просто, как присвоение URL-адреса в качестве атрибута src iframe. Пример: $("#preview").prop("src", $("#url").val()).

Edit: Обновлен код с исправлением. Также добавлена ​​новая функция js makeCollapsible() для ретро-активного преобразования ul в интерактивную, разборную древовидную структуру в соответствии с комментарием OP. Также добавлены соответствующие стили CSS для стиля древовидной структуры. Теперь дерево выглядит как снизу:

Collapsible, Clickable HTML Tree!

Ответ 2

Приложение. Это длинный ответ, но он решает конкретные проблемы и решения для предоставленных фрагментов кода. Надеюсь, вы и другие найдете, что стоит сравнить.:)


Сначала измените свой PHP, чтобы сделать чище JSON

При анализе DOM я рекомендую установить имена элементов из возвращаемого объекта в качестве ассоциативных ключей в $arr['child_nodes'] с помощью array_merge() вместо того, чтобы нажимать их на массив как индексированные элементы. Для этого $arr['child_nodes'] необходимо сначала определить как массив. Позже, если в него не будут объединены никакие элементы, вы просто unset перед тем, как $arr будет добавлен в основной объект.

Это делает окончательный результат JSON более простым для анализа, исключая необходимость использования вложенного цикла в вашем javascript при создании дерева.

Я также рекомендую вставлять условные проверки для ->length перед выполнением циклов foreach. Ваш существующий код выдавал сообщения "Предупреждение", когда элементы нулевой длины вводились в цикл.

Наконец, вы можете упростить свою логику для обработки типов node, заменив ваш текущий оператор 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 имела три недостатка:

  • Использование счетчика для создания идентификаторов "на лету", а затем использование jQuery для поиска ранее сделанных элементов с этими идентификаторами, для которых добавлять элементы списка, было утечкой производительности и запутыванием для отладки.

  • Использование .find() привело к тому, что jQuery избыточно вводил рекурсивный вызов и добавлял дочерние узлы с избыточным избытком в дерево.

  • Поскольку это обратный вызов отдельного асинхронного вызова, он может выполняться до завершения первого асинхронного вызова getHTML.php.

Переместите вызов асинхронного вызова, чтобы получить JSON в функцию обратного вызова при первом вызове асинхронного вызова, чтобы он не мог получить неполный или старый JSON с сервера.

Вы также должны использовать этот первый обратный вызов для установки iframe src и пустого контейнера #tree-sec, чтобы последующие действия не добавляли более одного дерева. Вы можете сделать то же самое, используя .replace() вместо .empty(), за которым следует .append().

Чтобы построить дерево, я рекомендую следующий простой подход, который рекурсивно строит список как строку, так что метод .append вызывается только один раз. Для больших деревьев это значительно улучшит производительность.

Вы можете ввести счетчик и динамически назначенные идентификаторы для этой функции, если хотите, но я оставил ее, чтобы более четко продемонстрировать, что они не нужны для построения дерева.

Я также рекомендую проверить наличие дочерних узлов перед вводом рекурсивного вызова. Выполнение этой проверки позволяет передать только объект дочерних узлов, который из-за нового JSON, полученного в результате изменений, внесенных в PHP script, теперь содержит имена тегов в качестве ключей вместо индексированных ключей с элементами в виде дочерних элементов. Если бы мы не упростили JSON, в этот момент потребовался бы второй цикл для извлечения каждого элемента.

Вы также заметите включение атрибутов aria- и role. Это позволит вам быть полностью доступным, если вы выберете.

Смотрите: Использование расширенного состояния WAI-ARIA для маркировки расширяемых и разборчивых областей (w3.org)

Он также предоставляет вам удобный и семантический способ управления CSS и переключением состояния, которое вы можете увидеть в дополнительном обработчике кликов, добавленном в нижней части script, и в примере 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

Построить многомерный массив PHP для ваших тегов HTML, а затем предоставить этот массив в качестве входных данных для встроенной функции php json_encode ($ array), и это возвращает дерево-структурированный json-вывод