Функция PHP substr (), которая позволяет установить начальную и конечную точку И сохраняет форматирование HTML?

С помощью обычной функции substr() в PHP у вас есть возможность решить, где вы хотите «начать» обрезку строки, а также установить длину. Длина, вероятно, используется больше всего, но в этом случае мне нужно обрезать около 120 символов с начала. Проблема в том, что мне нужно сохранить html в строке нетронутым и вырезать только фактический текст внутри тегов.

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

Вот один, который я нашел: Использование подстроки PHP () и strip_tags () с сохранением форматирования и без нарушения HTML

Итак, мне в основном нужна функция substr(), которая работает точно так же, как исходная, за исключением сохранения форматирования.

Какие-либо предложения?

Пример содержимого для изменения:

<p>Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going <a href="#">through the cites</a> of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus</p> <p>Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the <strong>Renaissance</strong>. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.</p>

После отсечения 5 с самого начала:

<p>ary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going <a href="#">through the cites</a> of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus</p> <p>Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the <strong>Renaissance</strong>. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.</p>

И 5 с начала И конца:

<p>ary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going <a href="#">through the cites</a> of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus</p> <p>Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the <strong>Renaissance</strong>. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.1</p>

Да, ты уловил мою мысль?

Я бы предпочел, чтобы он обрезал все слово, если бы он не разрезал середину одного, но это не очень важно.

** Изменить: ** Фиксированные кавычки.


person qwerty    schedule 03.01.2013    source источник
comment
Опубликуйте строку, которую нужно изменить. Может быть способ лучше, чем substr ()   -  person Michael Berkowski    schedule 03.01.2013
comment
Нет одной конкретной строки, которую мне нужно изменить, она будет отличаться. По сути, это набор <p> тегов и, возможно, несколько <a>, <strong> и т. Д. Внутри. Это содержимое страницы блога, созданное редактором WYSIWYG.   -  person qwerty    schedule 03.01.2013
comment
Затем опубликуйте образец. Для HTML, созданного случайным образом wysiwyg, вам будет очень сложно использовать substr. Может быть работа для правильного парсера DOM.   -  person Michael Berkowski    schedule 03.01.2013
comment
Можете ли вы опубликовать пример того, чего вы ожидаете при обрезании начала строки?   -  person Francis Avila    schedule 03.01.2013
comment
Хорошо, ребята, посмотрите образцы контента в OP. @ MichaelBerkowski   -  person qwerty    schedule 03.01.2013


Ответы (3)


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

  • Случайно разрезал элементы пополам.
  • Случайное разрезание энтузиастов пополам.
  • Не считая текста внутри элементов.
  • Убедитесь, что объект-персонаж считается одним символом.
  • Убедитесь, что все элементы правильно закрыты.
  • Убедитесь, что вы не уничтожили строку, потому что вы используете substr() в строке utf-8.

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

Однако решение DOM довольно простое: пройтись по всем текстовым узлам, подсчитав длину строк, и при необходимости удалить или подгруппировать их текстовое содержимое. Код ниже делает это:

$html = <<<'EOT'
<p>Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going <a href="#">through the cites</a> of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus</p> <p>Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the <strong>Renaissance</strong>. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.</p>
EOT;
function substr_html($html, $start, $length=null, $removeemptyelements=true) {
    if (is_int($length)) {
        if ($length===0) return '';
        $end = $start + $length;
    } else {
        $end = null;
    }
    $d = new DOMDocument();
    $d->loadHTML('<html><head><meta http-equiv="content-type" content="text/html;charset=utf-8"><title></title></head><body>'.$html.'</body>');
    $body = $d->getElementsByTagName('body')->item(0);
    $dxp = new DOMXPath($d);
    $t_start = 0; // text node's start pos relative to all text
    $t_end   = null; // text node's end pos relative to all text

    // copy because we may modify result of $textnodes
    $textnodes = iterator_to_array($dxp->query('/descendant::*/text()', $body));

// PHP 5.2 doesn't seem to implement Traversable on DOMNodeList,
// so `iterator_to_array()` won't work. Use this instead:
// $textnodelist = $dxp->query('/descendant::*/text()', $body);
// $textnodes = array();
// for ($i = 0; $i < $textnodelist->length; $i++) {
//  $textnodes[] = $textnodelist->item($i);
//}
//unset($textnodelist);

    foreach($textnodes as $text) {
        $t_end = $t_start + $text->length;
        $parent = $text->parentNode;
        if ($start >= $t_end || ($end!==null && $end < $t_start)) {
            $parent->removeChild($text);
        } else {
            $n_offset = max($start - $t_start, 0);
            $n_length = ($end===null) ? $text->length : $end - $t_start;
            if (!($n_offset===0 && $n_length >= $text->length)) {
                $substr = $text->substringData($n_offset, $n_length);
                if (strlen($substr)) {
                    $text->deleteData(0, $text->length);
                    $text->appendData($substr);
                } else {
                    $parent->removeChild($text);
                }
            }
        }

        // if removing this text emptied the parent of nodes, remove the node!
        if ($removeemptyelements && !$parent->hasChildNodes()) {
            $parent->parentNode->removeChild($parent);
        }

        $t_start = $t_end;
    }
    unset($textnodes);
    $newstr = $d->saveHTML($body);

    // mb_substr() is to remove <body></body> tags
    return mb_substr($newstr, 6, -7, 'utf-8');
}


echo substr_html($html, 480, 30);

Это выведет:

<p> of "de Finibus</p> <p>Bonorum et Mal</p>

Обратите внимание, это не сбивает с толку тот факт, что ваша «подстрока» охватывает несколько p элементов.

person Francis Avila    schedule 04.01.2013
comment
Выглядит потрясающе! Однако я получаю сообщение об ошибке на PHP 5.2.17. См. Здесь: pastebin.com/HvVN19tR Нужно ли мне добавить что-то еще? Любые идеи? - person qwerty; 04.01.2013
comment
Похоже, что интерфейс Traversable не был реализован в DOMNodeList в 5.2.17. (Я не могу поручиться за этот код как есть в чем-либо меньшем, чем 5.3.) Я внес в ответ обходной путь, но вам следует обновить его до версии 5.3, если это возможно. - person Francis Avila; 04.01.2013
comment
Я попробую, как только смогу, спасибо! Я посмотрю, что я могу сделать с обновлением PHP. Я могу просто подождать, пока выйдет 5.5 и обновится, потому что я точно хочу поиграть с этим. - person qwerty; 05.01.2013
comment
Новый код выполнил свою работу, но $d->saveHTML($body);, похоже, несовместим с 5.2. Я немного покопался и обнаружил, что saveXml() дает почти такой же результат (не уверен, что отличается, у меня он отлично работает). Пожалуйста, включите это в свой ответ, если кто-то еще столкнется с той же проблемой. Я также просто понял, что было бы намного лучше, если бы оно завершило последнее предложение, но я не буду беспокоиться о его изменении, если только это не простое редактирование? - person qwerty; 05.01.2013
comment
SaveXML не подходящей замены. Он самозакрывает элементы, которые не могут быть в HTML. В качестве обходного пути используйте обычный saveHTML() без аргумента и используйте preg_match(), чтобы захватить материал внутри элемента body (или preg_replace(), чтобы удалить материал за его пределами). - person Francis Avila; 05.01.2013
comment
Спасибо, я попробую! :) - person qwerty; 06.01.2013

Вот начало, использующее DOMDocument (синтаксический анализатор xml / html), RecursiveIteratorIterator (для легкого обхода рекурсивных структур) и пользовательские реализации DOMNodeList итератора, чтобы хорошо поиграть с RecursiveIteratorIterator.

Все это по-прежнему довольно небрежно (не возвращает копию, но действует по ссылке _5 _ / _ 6_), и у него нет причудливых функций обычного substr(), таких как отрицательные значения для $start и / или $length, но, похоже, пока он работает. Я уверен, что есть ошибки. Но он должен дать вам представление о том, как это сделать с DOMDocument.

Пользовательские итераторы:

class DOMNodeListIterator
    implements Iterator
{
    protected $domNodeList;

    protected $position;

    public function __construct( DOMNodeList $domNodeList )
    {
        $this->domNodeList = $domNodeList;
        $this->rewind();
    }

    public function valid()
    {
        return $this->position < $this->domNodeList->length;
    }

    public function next()
    {
        $this->position++;
    }

    public function key()
    {
        return $this->position;
    }

    public function rewind()
    {
        $this->position = 0;
    }

    public function current()
    {
        return $this->domNodeList->item( $this->position );
    }
}

class RecursiveDOMNodeListIterator
    extends DOMNodeListIterator
    implements RecursiveIterator
{
    public function hasChildren()
    {
        return $this->current()->hasChildNodes();
    }

    public function getChildren()
    {
        return new self( $this->current()->childNodes );
    }
}

Фактическая функция:

function DOMSubstr( DOMNode $domNode, $start = 0, $length = null )
{
    if( $start == 0 && ( $length == null || $length >= strlen( $domNode->nodeValue ) ) )
    {
        return;
    }

    $nodesToRemove = array();
    $rii = new RecursiveIteratorIterator( new RecursiveDOMNodeListIterator( $domNode->childNodes ), RecursiveIteratorIterator::SELF_FIRST );
    foreach( $rii as $node )
    {
        if( $start <= 0 && $length !== null && $length <= 0 )
        {
            /* can't remove immediately
             * because this will mess with
             * iterating over RecursiveIteratorIterator
             * so remember for removal, later on
             */
            $nodesToRemove[] = $node;
            continue;
        }

        if( $node->nodeType == XML_TEXT_NODE )
        {
            if( $start > 0 )
            {
                $count = min( $node->length, $start );
                $node->deleteData( 0, $count );
                $start -= $count;
            }

            if( $start <= 0 )
            {
                if( $length == null )
                {
                    break;
                }
                else if( $length <= 0 )
                {
                    continue;
                }
                else if( $length >= $node->length )
                {
                    $length -= $node->length;
                    continue;
                }
                else
                {
                    $node->deleteData( $length, $node->length - $length );
                    $length = 0;
                }
            }
        }
    }

    foreach( $nodesToRemove as $node )
    {
        $node->parentNode->removeChild( $node );
    }
}

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

$html = <<<HTML
<p>Just a short text sample with <a href="#">a link</a> and some trailing elements such as <strong>strong text<strong>, <em>emphasized text</em>, <del>deleted text</del> and <ins>inserted text</ins></p>
HTML;

$dom = new DomDocument();
$dom->loadHTML( $html );
/*
 * this is particularly sloppy:
 * I pass $dom->firstChild->nextSibling->firstChild (i.e. <body>)
 * because the function uses strlen( $domNode->nodeValue )
 * which will be 0 for DOMDocument itself
 * and I didn't want to utilize DOMXPath in the function
 * but perhaps I should have
 */
DOMSubstr( $dom->firstChild->nextSibling->firstChild, 8, 25 );

/*
 * passing a specific node to DOMDocument::saveHTML()
 * only works with PHP >= 5.3.6
 */
echo $dom->saveHTML( $dom->firstChild->nextSibling->firstChild->firstChild );
person Decent Dabbler    schedule 03.01.2013

Вы можете попробовать это, если это не более длинный текст (из-за времени выполнения).

но в этом случае мне нужно с самого начала отрезать около 120 символов.

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

И, пожалуйста, не переусердствуйте: это решение для коротких строк, и это не лучший способ сделать это, но это полный рабочий образец кода!

<?php
$text = "<a href='blablabla'>m</a>ylinks...<b>not this code is working</b>......";
$newtext = "";
$delete = 13;
$tagopen = false;

while ($text != ""){
    $checktag=$text[0];
    $text=substr( $text, 1 );
    if ($checktag =="<" || $tagopen == TRUE){
        $newtext .= $checktag;
        if ($checktag == ">"){
        $tagopen = FALSE;
        }
        else{
        $tagopen = TRUE;
        }
    }
    elseif ($delete > 0){   
        $delete = $delete -1 ;
        }
    else
    {
    $newtext .= $checktag;

    }
}
echo $newtext;



?>

он возвращает:

<a href='blablabla'></a><b> this code is working</b>......
person Yunalescar    schedule 03.01.2013
comment
А как насчет экранированных символов, таких как или подобных? Я полагаю, они будут отрезаны посередине? Мне также нужно уметь сокращать с обеих сторон, а не только с начала. - person qwerty; 03.01.2013
comment
Если вы намереваетесь сократить оба пути и также избежать специальных символов, я должен согласиться с Майклом Берковски, когда он прокомментировал ваш вопрос. У меня был вопрос в первой версии (как я тоже цитировал), вы сказали с самого начала, но не подумал об экранированных символах. Извините - person Yunalescar; 03.01.2013
comment
Ладно! Подойдет парсер DOM, не могли бы вы порекомендовать быстрый в установке и простой в использовании? - person qwerty; 03.01.2013