Как закрыть закрытые HTML-теги?
Всякий раз, когда мы извлекаем некоторый пользовательский контент с некоторым редактированием из базы данных или аналогичных источников, мы можем получить часть, которая содержит только тег открытия, но не закрывается.
Это может помешать текущему макету веб-сайта.
Есть ли у клиента или серверный способ его исправления?
Ответы
Ответ 1
Нашел отличный ответ для этого:
Используйте PHP 5 и используйте метод loadHTML() объекта DOMDocument. Этот автоматический анализ плохо сформированного HTML и последующий вызов saveXML() выдаст допустимый HTML. Функции DOM можно найти здесь:
http://www.php.net/dom
Использование этого:
$doc = new DOMDocument();
$doc->loadHTML($yourText);
$yourText = $doc->saveHTML();
Ответ 2
Вы можете использовать Tidy:
Tidy является связующим для утилиты чистки и восстановления Tidy HTML, которая позволяет не только очищать и иным образом манипулировать HTML-документами, но также перемещаться по дереву документов.
или HTMLPurifier
HTML-очиститель - это стандартно-совместимый Библиотека HTML-фильтра, написанная в PHP. Очиститель HTML не только удалит все вредоносные код (более известный как XSS) с тщательно проверенным, безопасный, но разрешающий белый список, он также гарантирует, что ваши документы совместимых стандартов, что только достижимо с всестороннее знание спецификаций W3C.
Ответ 3
У меня есть решение для php
<?php
// close opened html tags
function closetags ( $html )
{
#put all opened tags into an array
preg_match_all ( "#<([a-z]+)( .*)?(?!/)>#iU", $html, $result );
$openedtags = $result[1];
#put all closed tags into an array
preg_match_all ( "#</([a-z]+)>#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"); ?>
Ответ 4
В дополнение к серверным инструментам, таким как Tidy, вы также можете использовать браузер пользователя для выполнения некоторой очистки. Одна из самых замечательных вещей о innerHTML
заключается в том, что он будет применять один и тот же "на лету" ремонт к динамическому контенту, как и к HTML-страницам. Этот код работает очень хорошо (с двумя оговорками), и на самом деле ничего не записывается на страницу:
var divTemp = document.createElement('div');
divTemp.innerHTML = '<p id="myPara">these <i>tags aren\'t <strong> closed';
console.log(divTemp.innerHTML);
Предостережения:
-
Различные браузеры возвращают разные строки. Это не так уж плохо, за исключением IE, который вернет заглавные теги и разделит кавычки от атрибутов тегов, которые не пройдут проверку. Решение здесь - сделать простую очистку на стороне сервера. Но по крайней мере документ будет правильно структурирован XML.
-
Я подозреваю, что вам может потребоваться задержка перед чтением innerHTML - дать браузеру возможность переварить строку - или вы рискуете получить обратно именно то, что было введено. Я просто попробовал на IE8, и похоже, что строка сразу анализируется, но я не уверен в IE6. Вероятно, лучше было бы прочитать innerHTML после задержки (или выбросить его в setTimeout(), чтобы заставить его до конца очереди).
Я бы порекомендовал вам воспользоваться советом @Gordon и использовать Tidy, если у вас есть доступ к нему (требуется меньше усилий для реализации), и в противном случае используйте innerHTML и напишите свою собственную опрятную функцию в PHP.
И хотя это не является частью вашего вопроса, так как это для CMS, рассмотрите также использование YUI 2 Rich Text Editor для такие вещи. Это довольно легко реализовать, несколько легко настроить, интерфейс очень хорошо знаком большинству пользователей, и он выплескивает совершенно правильный код. Есть несколько других готовых текстовых редакторов, но YUI обладает лучшей лицензией и является самым мощным из всех, что я видел.
Ответ 5
Для фрагментов HTML и работы от ответа KJS У меня был успех со следующим, когда фрагмент имеет один корневой элемент:
$dom = new DOMDocument();
$dom->loadHTML($string);
$body = $dom->documentElement->firstChild->firstChild;
$string = $dom->saveHTML($body);
Без корневого элемента это возможно (но, похоже, только первый текстовый дочерний элемент node в 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();
Ответ 6
Лучшая функция PHP для удаления не открытых/не закрытых тегов из webmaster-glossar.de(me)
function closetag($html){
$html_new = $html;
preg_match_all ( "#<([a-z]+)( .*)?(?!/)>#iU", $html, $result1);
preg_match_all ( "#</([a-z]+)>#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
Ответ 7
Эрик Арвидссон написал хороший парсер SAX SAX в 2004 году. 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;
}
Ответ 8
Я использовал собственный метод 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)); // E.g. 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;
}
}