Как закрыть закрытые HTML-теги?

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

Это может помешать текущему расположению веб-сайта.

Есть ли способ для клиентов или серверов, чтобы это исправить?

Related of "Как закрыть закрытые HTML-теги?"

Нашел отличный ответ для этого:

Используйте PHP 5 и используйте метод loadHTML () объекта DOMDocument. Этот автоматический анализ плохо сформированного HTML и последующий вызов saveXML () выдаст допустимый HTML. Функции DOM можно найти здесь:

http://www.php.net/dom

Использование этого:

$doc = new DOMDocument(); $doc->loadHTML($yourText); $yourText = $doc->saveHTML(); 

Вы можете использовать Tidy :

Tidy является связующим звеном для утилиты очистки и восстановления Tidy HTML, которая позволяет не только очищать и иным образом манипулировать HTML-документами, но также перемещаться по дереву документов.

или HTMLPurifier

HTML Purifier – это стандартная библиотека фильтров HTML, написанная на PHP. HTML-очиститель не только удалит весь вредоносный код (более известный как XSS) с тщательно проверенным, безопасным, но и разрешенным «белым списком», он также гарантирует, что ваши документы совместимы со стандартами, что только достижимо с полным знанием спецификаций W3C.

У меня есть решение для php

 <?php // close opened html tags function closetags ( $html ) { #put all opened tags into an array preg_match_all ( "#<([az]+)( .*)?(?!/)>#iU", $html, $result ); $openedtags = $result[1]; #put all closed tags into an array preg_match_all ( "#</([az]+)>#iU", $html, $result ); $closedtags = $result[1]; $len_opened = count ( $openedtags ); # all tags are closed if( count ( $closedtags ) == $len_opened ) { return $html; } $openedtags = array_reverse ( $openedtags ); # close tags for( $i = 0; $i < $len_opened; $i++ ) { if ( !in_array ( $openedtags[$i], $closedtags ) ) { $html .= "</" . $openedtags[$i] . ">"; } else { unset ( $closedtags[array_search ( $openedtags[$i], $closedtags)] ); } } return $html; } // close opened html tags 

?>

вы можете использовать эту функцию как

  <?php echo closetags("your content <p>test test"); ?> 

В дополнение к серверным инструментам, таким как Tidy, вы также можете использовать браузер пользователя, чтобы выполнить некоторую очистку для вас. Одна из очень больших вещей о innerHTML заключается в том, что он будет применять тот же самый «на лету» ремонт для динамического контента, как и для HTML-страниц. Этот код работает очень хорошо (с двумя оговорками), и на самом деле ничего не записывается на страницу:

 var divTemp = document.createElement('div'); divTemp.innerHTML = '<p id="myPara">these <i>tags aren\'t <strong> closed'; console.log(divTemp.innerHTML); 

Оговорки:

  1. Различные браузеры возвращают разные строки. Это не так уж плохо, за исключением IE, который вернет заглавные теги и разделит кавычки от атрибутов тегов, которые не пройдут проверку. Решение здесь – сделать простую очистку на стороне сервера. Но, по крайней мере, документ будет правильно структурирован XML.

  2. Я подозреваю, что вам может потребоваться задержка перед чтением innerHTML – дать браузеру возможность переварить строку – или вы рискуете получить обратно именно то, что было введено. Я просто пробовал IE8, и это похоже на строку сразу анализируется, но я не уверен в IE6. Вероятно, лучше было бы прочитать innerHTML после задержки (или выбросить его в setTimeout (), чтобы заставить его до конца очереди).

Я бы порекомендовал вам воспользоваться советом @ Gordon и использовать Tidy, если у вас есть доступ к нему (требуется меньше усилий для реализации), и в противном случае используйте innerHTML и напишите свою собственную опрятную функцию в PHP.

И хотя это не является частью вашего вопроса, так как это для CMS, рассмотрите также использование YUI 2 Rich Text Editor для таких вещей. Его довольно легко реализовать, несколько легко настроить, интерфейс очень хорошо знаком большинству пользователей, и он выплескивает совершенно правильный код. Есть несколько других готовых текстовых редакторов, но YUI обладает лучшей лицензией и является самым мощным из всех, что я видел.

Для фрагментов HTML и работы с ответом KJS у меня был успех со следующим, когда фрагмент имеет один корневой элемент:

 $dom = new DOMDocument(); $dom->loadHTML($string); $body = $dom->documentElement->firstChild->firstChild; $string = $dom->saveHTML($body); 

Без корневого элемента это возможно (но, кажется, обертывает только первый текстовый дочерний узел в p-тегах в text <p>para</p> text ):

 $dom = new DOMDocument(); $dom->loadHTML($string); $bodyChildNodes = $dom->documentElement->firstChild->childNodes; $string = ''; foreach ($bodyChildNodes as $node){ $string .= $dom->saveHTML($node); } 

Или еще лучше, от PHP> = 5.4 и libxml> = 2.7.8 (2.7.7 для LIBXML_HTML_NOIMPLIED ):

 $dom = new DOMDocument(); // Load with no html/body tags and do not add a default dtd $dom->loadHTML($string, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $string = $dom->saveHTML(); 

Лучшая функция PHP для удаления не открытых / не закрытых тегов из webmaster-glossar.de (me)

 function closetag($html){ $html_new = $html; preg_match_all ( "#<([az]+)( .*)?(?!/)>#iU", $html, $result1); preg_match_all ( "#</([az]+)>#iU", $html, $result2); $results_start = $result1[1]; $results_end = $result2[1]; foreach($results_start AS $startag){ if(!in_array($startag, $results_end)){ $html_new = str_replace('<'.$startag.'>', '', $html_new); } } foreach($results_end AS $endtag){ if(!in_array($endtag, $results_start)){ $html_new = str_replace('</'.$endtag.'>', '', $html_new); } } return $html_new; } 

используйте эту функцию:

 closetag('i <b>love</b> my <strike>cat'); #output: i <b>love</b> my cat closetag('i <b>love</b> my cat</strike>'); #output: i <b>love</b> my cat 

В 2004 году Эрик Арвидсон написал хороший анализатор HTML SAX. http://erik.eae.net/archives/2004/11/20/12.18.31/

Он отслеживает открытые теги, поэтому с минималистичным обработчиком SAX можно вставить закрывающие теги в правильное положение:

 function tidyHTML(html) { var output = ''; HTMLParser(html, { comment: function(text) { // filter html comments }, chars: function(text) { output += text; }, start: function(tagName, attrs, unary) { output += '<' + tagName; for (var i = 0; i < attrs.length; i++) { output += ' ' + attrs[i].name + '='; if (attrs[i].value.indexOf('"') === -1) { output += '"' + attrs[i].value + '"'; } else if (attrs[i].value.indexOf('\'') === -1) { output += '\'' + attrs[i].value + '\''; } else { // value contains " and ' so it cannot contain spaces output += attrs[i].value; } } output += '>'; }, end: function(tagName) { output += '</' + tagName + '>'; } }); return output; } 

Я использовал метод DOMDocument, но с некоторыми улучшениями в безопасности.

Обратите внимание, что другие ответы, которые используют DOMDocument, не учитывают html-нити, такие как

 This is a <em>HTML</em> strand 

Вышесказанное фактически приведет к

 <p>This is a <em>HTML</em> strand 

Мое решение ниже

 function closeDanglingTags($html) { if (strpos($html, '<') || strpos($html, '>')) { // There are definitiley HTML tags $wrapped = false; if (strpos(trim($html), '<') !== 0) { // The HTML starts with a text node. Wrap it in an element with an id to prevent the software wrapping it with a <p> // that we know nothing about and cannot safely retrieve $html = cHE::getDivHtml($html, null, 'closedanglingtagswrapper'); $wrapped = true; } $doc = new DOMDocument(); $doc->encoding = 'utf-8'; @$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); if ($doc->firstChild) { // Test whether the firstchild is definitely a DOMDocumentType if ($doc->firstChild instanceof DOMDocumentType) { // Remove the added doctype $doc->removeChild($doc->firstChild); } } if ($wrapped) { // The contents originally started with a text node and was wrapped in a div#plasmappclibtextwrap. Take the contents // out of that div $node = $doc->getElementById('closedanglingtagswrapper'); $children = $node->childNodes; // The contents of the div. Equivalent to $('selector').children() $doc = new DOMDocument(); // Create a new document to add the contents to, equiv. to "var doc = $('<html></html>');" foreach ($children as $childnode) { $doc->appendChild($doc->importNode($childnode, true)); // Eg doc.append() } } // Remove the added html,body tags return trim(str_replace(array('<html><body>', '</body></html>'), '', html_entity_decode($doc->saveHTML()))); } else { return $html; } }