Сократить текст, не разбивая слова и не нарушая HTML-теги

Я пытаюсь обрезать текст после 236 символов, не разрезая слова пополам и сохраняя теги html. Это то, что я использую прямо сейчас:

$shortdesc = $_helper->productAttribute($_product, $_product->getShortDescription(), 'short_description');
$lenght = 236;
echo substr($shortdesc, 0, strrpos(substr($shortdesc, 0, $lenght), " "));

Хотя это работает в большинстве случаев, оно выигралоуважать теги HTML. Так, например, этот текст:

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. <strong>Stet clita kasd gubergren</strong>

будет отрезан с тегом, все еще открытым. Есть ли способ обрезать текст после 236 символов, кроме HTML-тегов?

 HamZa16 мая 2013 г., 11:30
Каков ожидаемый результат ? Если вы хотите только текст, вы можете удалить HTML-теги ...
 HamZa16 мая 2013 г., 11:32
@ Вау естьstrip_tags для этого: p
 edCoder29 сент. 2014 г., 13:43
Попробуйте эту ссылку, может помочь вамstackoverflow.com/a/26098951/3944217
 Adder16 мая 2013 г., 11:38
У меня есть решение здесь, но это монстр, который использует preg_split и preg_match, чтобы найти открытые и закрытые теги и отслеживает их, а затем закрывает открытые в конце.
 Maxim Khan-Magomedov16 мая 2013 г., 11:32
Попробуйте использоватьpreg_split () разделить текст по тегу.
 Waygood16 мая 2013 г., 11:31
Гугл замечательная вещьalanwhipple.com/2011/05/25/...
 PeeHaa16 мая 2013 г., 11:32
@ Вау, который не уважает теги
 wau16 мая 2013 г., 11:31
Удалить теги целиком?preg_replace ("]*>", "", $shortdesc)
 akDeveloper16 мая 2013 г., 12:49
Я использовал функцию, о которой упоминал @fulbaked, пока не нашелHTMLPurifier, Обрежьте текст по своему усмотрению и используйте HTMLPurifier для исправления закрытия отсутствующих тегов.
 PeeHaa16 мая 2013 г., 11:31
Вы должны будете использовать [HTML-парсер [(php.net/manual/en/class.domdocument.php) для этого и подсчитать все символы в текстовом узле для этого.

Ответы на вопрос(8)

Я сделал в JS, надеюсь, эта логика поможет и в PHP ..

splitText : function(content, count){
        var originalContent = content;
         content = content.substring(0, count);
          //If there is no occurance of matches before breaking point and the hit breakes in between html tags.
         if (content.lastIndexOf("")){
            content = content.substring(0, content.lastIndexOf('', originalContent.indexOf("

Вот решение JS:подрезать-HTML

Идея состоит в том, чтобы разделить строку HTML таким образом, чтобы получить массив с элементами, являющимися тегом html (открытым или закрытым) или просто строкой.

var arr = html.replace(//g, ">\n")
              .replace(/\n\n/g, "\n")
              .replace(/^\n/g, "")
              .replace(/\n$/g, "")
              .split("\n");

Чем мы можем перебирать массив и считать символы.

 Mathias Müller02 сент. 2014 г., 20:15
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится.
 Brankodd02 сент. 2014 г., 21:16
@ MathiasMüСпасибо за предложение, я думаю, что код слишком большой, чтобы вставить здесь, я добавил идею позади него.

Вы можете использовать подход XML и помещать элементы в строку var, пока длина строки не превысит 236

пример кода?

for each node // text or tag
  push to the string var

  if string length > 236
    break

endfor

для разбора HTML в PHPhttp://simplehtmldom.sourceforge.net/

Это должно сделать это:

class Html
{
    protected
        $reachedLimit = false,
        $totalLen = 0,
        $maxLen = 25,
        $toRemove = array();

    public static function trim($html, $maxLen = 25)
    {

        $dom = new DomDocument();

        if (version_compare(PHP_VERSION, '5.4.0') < 0) {
            $dom->loadHTML($html);
        } else {
            $dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        }

        $instance = new static();
        $toRemove = $instance->walk($dom, $maxLen);

        // remove any nodes that exceed limit
        foreach ($toRemove as $child) {
            $child->parentNode->removeChild($child);
        }

        // remove wrapper tags added by DD (doctype, html...)
        if (version_compare(PHP_VERSION, '5.4.0') < 0) {
            // http://stackoverflow.com/a/6953808/1058140
            $dom->removeChild($dom->firstChild);
            $dom->replaceChild($dom->firstChild->firstChild->firstChild, $dom->firstChild);

            return $dom->saveHTML();
        }

        return $dom->saveHTML();
    }

    protected function walk(DomNode $node, $maxLen)
    {

        if ($this->reachedLimit) {
            $this->toRemove[] = $node;
        } else {
            // only text nodes should have text,
            // so do the splitting here
            if ($node instanceof DomText) {
                $this->totalLen += $nodeLen = strlen($node->nodeValue);

                // use mb_strlen / mb_substr for UTF-8 support
                if ($this->totalLen > $maxLen) {
                    $node->nodeValue = substr($node->nodeValue, 0, $nodeLen - ($this->totalLen - $maxLen)) . '...';
                    $this->reachedLimit = true;
                }
            }

            // if node has children, walk its child elements
            if (isset($node->childNodes)) {
                foreach ($node->childNodes as $child) {
                    $this->walk($child, $maxLen);
                }
            }
        }

        return $this->toRemove;
    }
}

Используйте как:$str = Html::trim($str, 236);

(демо здесь)

Некоторые сравнения производительности между этим и cakePHP 'с регулярное решение

Там'С очень небольшой разницей, и при очень больших размерах строк, DomDocument на самом деле быстрее. Надежность, на мой взгляд, важнее, чем экономить несколько микросекунд.

 Sam Holguin16 окт. 2015 г., 00:12
Это действительно хорошее решение, спасибо!
 Joseph13 апр. 2014 г., 18:15
Это нея не работаю Я должен был изменитьif(isset($node->childNodes)) вif ( $node->hasChildNodes())
 angelcool.net12 авг. 2017 г., 01:13
кажется, что это делает utf8_decode @AlexWeinstein
 Alex Weinstein05 дек. 2016 г., 05:58
У меня не работал Unicode, даже при использовании mb_strlen () / mb_substr (), как было предложено.

Могу я просто подумать?

Пример текста :

Lorem ipsum dolor sit amet, magna aliquyam erat, duo dolores et ea rebum. <strong>Stet clita kasd gubergren</strong> hello

Сначала разберите его на:

array(
    '0' => array(
        'tag' => '',
        'text' => 'Lorem ipsum dolor sit amet, '
    ),
    '1' => array(
        'tag' => '',
        'text' => 'magna aliquyam erat',
    )
    '2' => ......
    '3' => ......
)

затем обрежьте текст по одному и оберните каждый тегом после обрезки,

тогда присоединяйтесь к ним.

 PeeHaa16 мая 2013 г., 11:44
Как насчет вложенных тегов?
 PeeHaa16 мая 2013 г., 11:48
Не мой вопрос Но мой опыт заключается в том, что при работе над такими вещами всегда имейте в виду такие вещи, как вложенность заранее.
 Phoenix16 мая 2013 г., 11:47
@PeeHaa 埽 конечно, вы можете использовать метод similiar для работы с вложенными тегами, но вы этого не сделалине нужно говорить с вложенными тегами в вашем вопросе

Я использую только регулярное выражение (в nodeJS, но нас не волнует язык):

// I want 600 caracters max in my text
const maxLength = 600;
// Get number of caracters to delete
const lgt = txt.length - maxLength;
// we leave X characters to allow the regex to adapt (Important to not only set (.{${lgt}}))
const reg = new RegExp(`[-,;!?.():]?\\s([^\\s]|]*>)*(.{${lgt},${lgt + 10}})

Я использую только регулярное выражение (в nodeJS, но нас не волнует язык):

, 'i'); // /[-,;!?.():]?\s([^\s]|]*>)*(.{600,610})$/ // replace all endind caracters by (...) return txt.replace(reg, ' (...)');

Он заменит все последние символы без тегов вырезания и удалит последний пробел.

Работа, например, с тегами как<br>, это не будет сокращать это ...

Замечания:Он не закроет все открытые теги, этоне в моем оригинальном вопросе

function limitStrlen($input, $length, $ellipses = true, $strip_html = true, $skip_html) 
{
    // strip tags, if desired
    if ($strip_html || !$skip_html) 
    {
        $input = strip_tags($input);

        // no need to trim, already shorter than trim length
        if (strlen($input) 

Лучшее решение, с которым я столкнулся, это класс CakePHP TextHelper.

Вот метод

/**
* Truncates text.
*
* Cuts a string to the length of $length and replaces the last characters
* with the ending if the text is longer than length.
*
* ### Options:
*
* - `ending` Will be used as Ending and appended to the trimmed string
* - `exact` If false, $text will not be cut mid-word
* - `html` If true, HTML tags would be handled correctly
*
* @param string  $text String to truncate.
* @param integer $length Length of returned string, including ellipsis.
* @param array $options An array of html attributes and options.
* @return string Trimmed string.
* @access public
* @link http://book.cakephp.org/view/1469/Text#truncate-1625
*/
function truncate($text, $length = 100, $options = array()) {
    $default = array(
        'ending' => '...', 'exact' => true, 'html' => false
    );
    $options = array_merge($default, $options);
    extract($options);

    if ($html) {
        if (mb_strlen(preg_replace('//', '', $text))  $length) {
                $left = $length - $totalLength;
                $entitiesLength = 0;
                if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) {
                    foreach ($entities[0] as $entity) {
                        if ($entity[1] + 1 - $entitiesLength = $length) {
                break;
            }
        }
    } else {
        if (mb_strlen($text)  '')); 
?>

Выход:

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. <strong>Stet clita kasd gubegre</strong>

Обратите внимание, что вывод заканчивается только после завершения последнего слова, но включает полные сильные теги

 Adder16 мая 2013 г., 11:51
@loeffel: вам нужно включить режим html, предоставив массив опций с html true:truncate($text, 40, array('html'=>true))
 fullybaked16 мая 2013 г., 12:01
@ Аддер просто к твоему сведениюЯ проверил этот метод еще раз, как яЯ никогда не сталкивался с вашими заявленными проблемами. Используя оба"e; а также в строке вместе с некоторыми другими объектами функция продолжала работать, как и ожидалось.
 Adder16 мая 2013 г., 12:15
Попробуйте текст с 10 русскими символами, например, в кодировке. &# 1041; каждый и обрезать его до 20 символов и использовать стандартное окончание "..» и режим html и точное усечение, и вы не получите точно такие же 10 русских символов.
 fullybaked16 мая 2013 г., 12:35
@Adder, но это больше связано с PHP 'Проблемы с Unicode при работе с его нативными строками? чем что-то конкретно не так с этой функцией. Если требуются многобайтовые символы Юникода, то необходимо что-то, что может обрабатывать их конкретно, но это можно добавить к этому методу.
 loeffel16 мая 2013 г., 11:47
Это выглядит действительно многообещающе, но вот что происходит с моим <сильный> тег в данном примере: <stro ... div = "" <= "">
 Adder16 мая 2013 г., 11:43
Это хорошая функция, но я думаю, что CakePHP-Funktion предназначен для усечения материала, записанного в БД, а не текста, который содержит много сущностей, то есть он усекает представление, а не видимые символы в выводе HTML, которые видит пользователь. Если это не изменилось в то время, когда я смотрел в последний раз на это.
 Adder16 мая 2013 г., 11:49
@fulbaked: я знаю эту функцию, и у нее есть проблемы с текстом, который содержит много HTML-объектов. Проблема начинается с того, что он определяет длину представления, а не отображаемых символов. Если это предназначено для Представлений, это нене работает, когда текст содержит, например, &Quot; или же &# 5050;
 fullybaked16 мая 2013 г., 11:38
Не хочешь сказать, почему тамСнизить голос?
 Adder16 мая 2013 г., 14:20
Функция действительно хорошо работает с обычным текстом Unicode, она просто неt обрабатывать HTML-объекты правильно. Это проблема, если вы применили htmlspecialschars или filter_input / filter_var для ввода по соображениям безопасности. И да, моя версия truncate сильно модифицирована для работы с сущностями.
 fullybaked16 мая 2013 г., 11:44
@Adder этот метод взят из TextHelper, используемого в представлениях, и не имеет никакого отношения к БД. Именно для случая использования в оригинальном вопросе.
 fullybaked16 мая 2013 г., 11:54
Посмотрите мои правки с примерами запуска текста через метод в приложении CakePHP.м работаю прямо сейчас и использую этот метод

Ваш ответ на вопрос