Intereting Posts
Ошибка соединения с андроидным php? (добавить java-код) XCache: невозможно запустить в приложении Laravel 5.1 Каков «правильный» способ предоставления приложения Zend с помощью обработчика базы данных (PHP) Как использовать crypt () с CRYPT_BLOWFISH? нужно регулярное выражение для анализа файла csv с двойными кавычками в php Захват разрешения экрана и отображение результатов на основе размера wikipedia api: получить только синтаксический анализ Prestashop фильтрует продукты по категориям с помощью WebService Прототипное наследование в PHP (например, в JavaScript) отсутствует) после списка аргументов в коде формы html Как удалить элемент массива, а затем повторно индексировать массив? PHP: как повторно использовать код (oop)? PHP: доступ к Bluetooth GPS через последовательный порт (SPP) COM3 Перенаправить динамические URL-адреса, включая запрос с htaccess Включить: css с расширением php-файла?

Вырезать ввод html при сохранении тегов с помощью PHP

Мне нужно вырезать html-вход на определенную длину, сохраняя при этом теги. Общая длина должна включать теги html . Я не могу найти решение, которое включает длину тега html в конечной длине. Как я могу вырезать html-вход на определенную длину без нарушения тегов и убедиться, что все открывающие теги имеют закрытие?

В принципе, я получаю html-ввод от пользователя, и мне нужно сделать его подходящим в определенной длины. Для этого я не хочу нарушать какие-либо теги html, но мне нужно убедиться, что общая длина (включая теги) меньше максимальной.

Например, вырезая эту строку до 20 символов:

<p>this is an example</p> 

должен дать результат

 <p>this is an ex</p> 

И это сокращение до 50 символов

 <p>this <a href="http://example.com">click me</a>jiasd</p> 

Должен дать

 <p>this <a href="http://example.com">click</a></p> 

Я пробовал это решение, и он хорошо работает для резки текста определенной длины, но я не могу найти способ подсчитать длину тега в общей сумме:

 function truncateHtml($text, $length = 100, $ending = '...', $exact = false, $considerHtml = true) { if ($considerHtml) { // if the plain text is shorter than the maximum length, return the whole text if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { return $text; } // splits all html-tags to scanable lines preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER); $total_length = strlen($ending); $open_tags = array(); $truncate = ''; foreach ($lines as $line_matchings) { // if there is any html-tag in this line, handle it and add it (uncounted) to the output if (!empty($line_matchings[1])) { // if it's an "empty element" with or without xhtml-conform closing slash if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) { // do nothing // if tag is a closing tag } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) { // delete tag from $open_tags list $pos = array_search($tag_matchings[1], $open_tags); if ($pos !== false) { unset($open_tags[$pos]); } // if tag is an opening tag } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) { // add tag to the beginning of $open_tags list array_unshift($open_tags, strtolower($tag_matchings[1])); } // add html-tag to $truncate'd text $truncate .= $line_matchings[1]; } // calculate the length of the plain text part of the line; handle entities as one character $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2])); if ($total_length+$content_length> $length) { // the number of characters which are left $left = $length - $total_length; $entities_length = 0; // search for html entities if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) { // calculate the real length of all entities in the legal range foreach ($entities[0] as $entity) { if ($entity[1]+1-$entities_length <= $left) { $left--; $entities_length += strlen($entity[0]); } else { // no more characters left break; } } } $truncate .= substr($line_matchings[2], 0, $left+$entities_length); // maximum lenght is reached, so get off the loop break; } else { $truncate .= $line_matchings[2]; $total_length += $content_length; } // if the maximum length is reached, get off the loop if($total_length>= $length) { break; } } } else { if (strlen($text) <= $length) { return $text; } else { $truncate = substr($text, 0, $length - strlen($ending)); } } // if the words shouldn't be cut in the middle... if (!$exact) { // ...search the last occurance of a space... $spacepos = strrpos($truncate, ' '); if (isset($spacepos)) { // ...and cut the text in this position $truncate = substr($truncate, 0, $spacepos); } } // add the defined ending to the text $truncate .= $ending; if($considerHtml) { // close all unclosed html-tags foreach ($open_tags as $tag) { $truncate .= '</' . $tag . '>'; } } return $truncate; } не function truncateHtml($text, $length = 100, $ending = '...', $exact = false, $considerHtml = true) { if ($considerHtml) { // if the plain text is shorter than the maximum length, return the whole text if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { return $text; } // splits all html-tags to scanable lines preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER); $total_length = strlen($ending); $open_tags = array(); $truncate = ''; foreach ($lines as $line_matchings) { // if there is any html-tag in this line, handle it and add it (uncounted) to the output if (!empty($line_matchings[1])) { // if it's an "empty element" with or without xhtml-conform closing slash if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) { // do nothing // if tag is a closing tag } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) { // delete tag from $open_tags list $pos = array_search($tag_matchings[1], $open_tags); if ($pos !== false) { unset($open_tags[$pos]); } // if tag is an opening tag } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) { // add tag to the beginning of $open_tags list array_unshift($open_tags, strtolower($tag_matchings[1])); } // add html-tag to $truncate'd text $truncate .= $line_matchings[1]; } // calculate the length of the plain text part of the line; handle entities as one character $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2])); if ($total_length+$content_length> $length) { // the number of characters which are left $left = $length - $total_length; $entities_length = 0; // search for html entities if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) { // calculate the real length of all entities in the legal range foreach ($entities[0] as $entity) { if ($entity[1]+1-$entities_length <= $left) { $left--; $entities_length += strlen($entity[0]); } else { // no more characters left break; } } } $truncate .= substr($line_matchings[2], 0, $left+$entities_length); // maximum lenght is reached, so get off the loop break; } else { $truncate .= $line_matchings[2]; $total_length += $content_length; } // if the maximum length is reached, get off the loop if($total_length>= $length) { break; } } } else { if (strlen($text) <= $length) { return $text; } else { $truncate = substr($text, 0, $length - strlen($ending)); } } // if the words shouldn't be cut in the middle... if (!$exact) { // ...search the last occurance of a space... $spacepos = strrpos($truncate, ' '); if (isset($spacepos)) { // ...and cut the text in this position $truncate = substr($truncate, 0, $spacepos); } } // add the defined ending to the text $truncate .= $ending; if($considerHtml) { // close all unclosed html-tags foreach ($open_tags as $tag) { $truncate .= '</' . $tag . '>'; } } return $truncate; } 

Для регулярных выражений нелегко запомнить длину и вхождения при перемещении символов, чтобы вы могли следовать решению в сочетании с RegEx и DOM (это обходное решение в основном выполняется с помощью DOM).

Вся эта философия такова:

1- Если последний узел является DOMElement или DOMText :

  • Если удалив этот узел, общая длина будет больше предела -> удалите его.
  • Если, удалив этот узел, общая длина будет меньше предела -> обрезать.
  • Общая длина> предел? Recurs (1): возвращает результат.
 function truncateHTML($html, $limit = 20) { static $wrapper = null; static $wrapperLength = 0; // trim unwanted CR/LF characters $html = trim($html); // Remove HTML comments $html = preg_replace("~<!--.*?-->~", '', $html); // If $html in in plain text if ((strlen(strip_tags($html)) > 0) && strlen(strip_tags($html)) == strlen($html)) { return substr($html, 0, $limit); } // If $html doesn't have a root element elseif (is_null($wrapper)) { if (!preg_match("~^\s*<[^\s!?]~", $html)) { // Defining a tag as our HTML wrapper $wrapper = 'div'; $htmlWrapper = "<$wrapper></$wrapper>"; $wrapperLength = strlen($htmlWrapper); $html = preg_replace("~><~", ">$html<", $htmlWrapper); } } // Calculating total length $totalLength = strlen($html); // If our input length is less than limit, we are done. if ($totalLength <= $limit) { if ($wrapper) { return preg_replace("~^<$wrapper>|</$wrapper>$~", "", $html); } return strlen(strip_tags($html)) > 0 ? $html : ''; } // Initializing a DOM object to hold our HTML $dom = new DOMDocument; $dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); // Initializing a DOMXPath object to query on our DOM $xpath = new DOMXPath($dom); // Query last node (this does not include comment or text nodes) $lastNode = $xpath->query("./*[last()]")->item(0); // While manipulating, when there is no HTML element left if ($totalLength > $limit && is_null($lastNode)) { if (strlen(strip_tags($html)) >= $limit) { $textNode = $xpath->query("//text()")->item(0); if ($wrapper) { $textNode->nodeValue = substr($textNode->nodeValue, 0, $limit ); $html = $dom->saveHTML(); return preg_replace("~^<$wrapper>|</$wrapper>$~", "", $html); } else { $lengthAllowed = $limit - ($totalLength - strlen($textNode->nodeValue)); if ($lengthAllowed <= 0) { return ''; } $textNode->nodeValue = substr($textNode->nodeValue, 0, $lengthAllowed); $html = $dom->saveHTML(); return strlen(strip_tags($html)) > 0 ? $html : ''; } } else { $textNode = $xpath->query("//text()")->item(0); $textNode->nodeValue = substr($textNode->nodeValue, 0, -(($totalLength - ($wrapperLength > 0 ? $wrapperLength : 0)) - $limit)); $html = $dom->saveHTML(); return strlen(strip_tags($html)) > 0 ? $html : ''; } } // If we have a text node after our last HTML element elseif ($nextNode = $lastNode->nextSibling) { if ($nextNode->nodeType === 3 /* DOMText */) { $nodeLength = strlen($nextNode->nodeValue); // If by removing our text node total length will be greater than limit if (($totalLength - ($wrapperLength > 0 ? $wrapperLength : 0)) - $nodeLength >= $limit) { // We should remove it $nextNode->parentNode->removeChild($nextNode); $html = $dom->saveHTML(); return truncateHTML($html, $limit); } // If by removing our text node total length will be less than limit else { // We should truncate our text to fit the limit $nextNode->nodeValue = substr($nextNode->nodeValue, 0, ($limit - (($totalLength - ($wrapperLength > 0 ? $wrapperLength : 0)) - $nodeLength))); $html = $dom->saveHTML(); // Caring about custom wrapper if ($wrapper) { return preg_replace("~^<$wrapper>|</$wrapper>$~", "", $html); } return $html; } } } // If current node is an HTML element elseif ($lastNode->nodeType === 1 /* DOMElement */) { $nodeLength = strlen($lastNode->nodeValue); // If by removing current HTML element total length will be greater than limit if (($totalLength - ($wrapperLength > 0 ? $wrapperLength : 0)) - $nodeLength >= $limit) { // We should remove it $lastNode->parentNode->removeChild($lastNode); $html = $dom->saveHTML(); return truncateHTML($html, $limit); } // If by removing current HTML element total length will be less than limit else { // We should truncate our node value to fit the limit $lastNode->nodeValue = substr($lastNode->nodeValue, 0, ($limit - (($totalLength - ($wrapperLength > 0 ? $wrapperLength : 0)) - $nodeLength))); $html = $dom->saveHTML(); if ($wrapper) { return preg_replace("~^<$wrapper>|</$wrapper>$~", "", $html); } return $html; } } } 

Примеры

1- Предоставление ввода, как показано ниже: $limit = 16 :

 <div>some data from <span class="first">blahblah test</span> was <span class="second">good</span>test<p> something</p><span>letter</span></div> 

Выполним HTML-код, который будет проиллюстрирован шаг за шагом:

 Step 0: <div>some data from <span class="first">blahblah test</span> was <span class="second">good</span>test<p> something</p><span>letter</span></div> Step 1: <div>some data from <span class="first">blahblah test</span> was <span class="second">good</span>test<p> something</p></div> Step 2: <div>some data from <span class="first">blahblah test</span> was <span class="second">good</span>test</div> Step 3: <div>some data from <span class="first">blahblah test</span> was <span class="second">good</span></div> Step 4: <div>some data from <span class="first">blahblah test</span> was </div> Step 5: <div>some data from <span class="first">blahblah test</span></div> Step 6: <div>some data from </div> Step 7: <div>some </div> 

2- На вашем собственном примере этот ввод с $limit = 50 :

 <p>this <a href="http://example.com">click me</a>jiasd</p> 

Выведет ожидаемый HTML:

 <p>this <a href="http://example.com">click</a></p> 

3- Обычные тексты обрабатываются одинаково ( $limit = 10 ):

 Hi how are you doing? 

Вывод:

 Hi how are 

4- HTML комментарии включены ( $limit = 10 ):

 <div>some data from <span class="first">blahblah test</span> was <span class="second">good</span>test<p> something</p><span class="text">hola</span><!-- comment --></div> 

Вывод:

 string(0) "" 

Почему пусто? Потому что, когда функция достигает последнего, но только одного шага, она видит <div></div> , длина которой равна 11 . Мы ничего не можем с этим сделать, поэтому полностью удалим его.

5- Вывод последнего примера с $limit = 12 :

 <div>s</div> 

Демо-версия PHP

Вы должны извлечь теги HTML из усеченной строки. Затем подсчитайте эти символы и снова обрезайте текстовую часть строки.

Обратите внимание, что следующее регулярное выражение соответствует всем html-тегам со всеми атрибутами (как открывающими, так и закрывающими тегами).

Перед окончанием return $truncate; :

 preg_match_all('/<[^>]+>/', $truncate, $html_tags); $html_tags_length = strlen(implode('', $html_tags)); 

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

Это немного эвристический, но он должен работать.

 function truncateHtml($text, $length = 100) { $current_size = strlen($text); $diff = strlen($text); $remainder = $current_size - $length; while($diff > 0 AND $remainder > 0) { $pattern = "/(.*)[^<>](?=<)/s"; $text = preg_replace($pattern, "$1", $text); $diff = $current_size - strlen($text); $current_size = strlen($text); $remainder = $current_size - $length; } // iff $diff == 0 there are no more characters to remove // iff $remainder == 0 there should removed no more characters return $text; } 

Здесь работает код.

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

Пример:

 <?php function htmlTrim($inStr, $length) { $c = 0; $outStr = preg_replace_callback( "/<.*?>|[^<>]*/", function($str) use (& $c, $length){ $str = $str[0]; if ($str && $str[0] == "<") { // Is tag. return $str; } else { if ($c >= $length) return ""; // Lenght already exceeded. $l = strlen($str); $c += $l; $overflow = $c - $length; if ($overflow > 0) { return substr($str, 0, $l - $overflow); } else { return $str; } }; }, $inStr ); return $outStr; }; echo htmlTrim("<span>Hello <b>World foobar</b></span>", 11);