Wordwrap / вырезать текст в строке HTML - PullRequest
3 голосов
/ 13 декабря 2011

вот что я хочу сделать: у меня есть строка, содержащая теги HTML, и я хочу вырезать ее, используя функцию wordwrap, исключая теги HTML.

Я застрял:

public function textWrap($string, $width)
{
    $dom = new DOMDocument();
    $dom->loadHTML($string);
    foreach ($dom->getElementsByTagName('*') as $elem)
    {
        foreach ($elem->childNodes as $node)
        {
            if ($node->nodeType === XML_TEXT_NODE)
            {
                $text = trim($node->nodeValue);
                $length = mb_strlen($text);
                $width -= $length;
                if($width <= 0)
                { 
                    // Here, I would like to delete all next nodes
                    // and cut the current nodeValue and finally return the string 
                }
            }
        }
    }
}

Я не уверен, что сейчас делаю это правильно. Надеюсь понятно ...

РЕДАКТИРОВАТЬ:

Вот пример. У меня есть этот текст

    <p>
        <span class="Underline"><span class="Bold">Test to be cut</span></span>
   </p><p>Some text</p>

Допустим, я хочу сократить его до 6-го символа, я хотел бы вернуть это:

<p>
    <span class="Underline"><span class="Bold">Test to</span></span>
</p>

Ответы [ 2 ]

3 голосов
/ 13 декабря 2011

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

Прежде всего я устанавливаю DOMDocument, содержащий фрагмент HTML, а затем выбираю тело, которое представляет его в DOM:

$htmlFragment = <<<HTML
<p>
        <span class="Underline"><span class="Bold">Test to be cut</span></span>
   </p><p>Some text </p>
HTML;

$dom = new DOMDocument();
$dom->loadHTML($htmlFragment);
$parent = $dom->getElementsByTagName('body')->item(0);
if (!$parent)
{
    throw new Exception('Parent element not found.');
}

Затем я использую свой класс TextRange, чтобы найти место, где необходимо выполнить разрез, и я использую TextRange, чтобы фактически выполнить разрез, и нахожу DOMNode, который должен стать последним узлом фрагмента:

$range = new TextRange($parent);

// find position where to cut the HTML textual represenation
// by looking for a word or the at least matching whitespace
// with a regular expression. 
$width = 17;
$pattern = sprintf('~^.{0,%d}(?<=\S)(?=\s)|^.{0,%1$d}(?=\s)~su', $width);
$r = preg_match($pattern, $range, $matches);
if (FALSE === $r)
{
    throw new Exception('Wordcut regex failed.');
}
if (!$r)
{
    throw new Exception(sprintf('Text "%s" is not cut-able (should not happen).', $range));
}

Это регулярное выражение находит смещение, где вырезать вещи в текстовом представлении, доступном $range. Шаблон регулярного выражения вдохновлен другим ответом , который обсуждает его более подробно и был немного изменен для соответствия потребностям этих ответов.

// chop-off the textnodes to make a cut in DOM possible
$range->split($matches[0]);
$nodes = $range->getNodes();
$cutPosition = end($nodes);

Поскольку вполне возможно, что нечего резать (например, body станет пустым), мне нужно разобраться с этим особым случаем. В противном случае, как отмечено в комментарии, необходимо удалить все следующих узлов:

// obtain list of elements to remove with xpath
if (FALSE === $cutPosition)
{
    // if there is no node, delete all parent children
    $cutPosition = $parent;
    $xpath = 'child::node()';
}
else
{
    $xpath = 'following::node()';
}

Остальное прямо: запрос xpath, удаление узлов и вывод результата:

// execute xpath
$xp = new DOMXPath($dom);
$remove = $xp->query($xpath, $cutPosition);
if (!$remove)
{
    throw new Exception('XPath query failed to obtain elements to remove');
}

// remove nodes
foreach($remove as $node)
{
    $node->parentNode->removeChild($node);
}

// inner HTML (PHP >= 5.3.6)
foreach($parent->childNodes as $node)
{
    echo $dom->saveHTML($node);
}

Полный пример кода доступен на кодовой панели Viper вкл. TextRange класс. На кодовой панели есть ошибка, поэтому ее результат неверен (Связано: Порядок результатов запроса XPath ). Фактический результат следующий:

<p>
        <span class="Underline"><span class="Bold">Test to</span></span></p>

Так что позаботьтесь о том, чтобы у вас была текущая версия libxml (обычно это так), а вывод foreach в конце использует функцию PHP saveHTML, которая доступна с этим параметром начиная с PHP 5.3.6. Если у вас нет этой версии PHP, возьмите альтернативу, описанную в Как получить XML-содержимое узла в виде строки? или аналогичный вопрос.

Если вы внимательно посмотрите на мой пример кода, вы можете заметить, что длина обрезки довольно большая ($width = 17;). Это потому, что перед текстом много пробельных символов. Это можно изменить, сделав регулярное выражение пропущенным на любое количество пробелов перед ним и / или сначала обрезав TextRange. Второй вариант требует большей функциональности, я написал что-то быстрое, что можно использовать после создания начального диапазона:

...
$range = new TextRange($parent);
$trimmer = new TextRangeTrimmer($range);
$trimmer->trim();
...

Это уберет лишние пробелы слева и справа внутри вашего HTML-фрагмента. Код TextRangeTrimmer следующий:

class TextRangeTrimmer
{
    /**
     * @var TextRange
     */
    private $range;

    /**
     * @var array
     */
    private $charlist;

    public function __construct(TextRange $range, Array $charlist = NULL)
    {
        $this->range = $range;
        $this->setCharlist($charlist);      
    }
    /**
     * @param array $charlist list of UTF-8 encoded characters
     * @throws InvalidArgumentException
     */
    public function setCharlist(Array $charlist = NULL)
    {
         if (NULL === $charlist)
            $charlist = str_split(" \t\n\r\0\x0B")
        ;

        $list = array();

        foreach($charlist as $char)
        {
            if (!is_string($char))
            {
                throw new InvalidArgumentException('Not an Array of strings.');
            }
            if (strlen($char))
            {
                $list[] = $char; 
            }
        }

        $this->charlist = array_flip($list);
    }
    /**
     * @return array characters
     */
    public function getCharlist()
    {
        return array_keys($this->charlist);
    }
    public function trim()
    {
        if (!$this->charlist) return;
        $this->ltrim();
        $this->rtrim();
    }
    /**
     * number of consecutive charcters of $charlist from $start to $direction
     * 
     * @param array $charlist
     * @param int $start offset
     * @param int $direction 1: forward, -1: backward
     * @throws InvalidArgumentException
     */
    private function lengthOfCharacterSequence(Array $charlist, $start, $direction = 1)
    {
        $start = (int) $start;              
        $direction = max(-1, min(1, $direction));
        if (!$direction) throw new InvalidArgumentException('Direction must be 1 or -1.');

        $count = 0;
        for(;$char = $this->range->getCharacter($start), $char !== ''; $start += $direction, $count++)
            if (!isset($charlist[$char])) break;

        return $count;
    }
    public function ltrim()
    {
        $count = $this->lengthOfCharacterSequence($this->charlist, 0);

        if ($count)
        {
            $remainder = $this->range->split($count);
            foreach($this->range->getNodes() as $textNode)
            {
                $textNode->parentNode->removeChild($textNode);
            }
            $this->range->setNodes($remainder->getNodes());
        }

    }
    public function rtrim()
    {
        $count = $this->lengthOfCharacterSequence($this->charlist, -1, -1);

        if ($count)
        {
            $chop = $this->range->split(-$count);
            foreach($chop->getNodes() as $textNode)
            {
                $textNode->parentNode->removeChild($textNode);
            }
        }
    }
}

Надеюсь, это полезно.

0 голосов
/ 13 января 2015

Если использование парсинга DOM не является целью, и вам нужно только транслировать HTML - взгляните на функцию cot_string_truncate в этой Gist .Это взято из Cotonti CMF.

Он также обрабатывает обычный текст или HTML.Вы можете установить длину и выбрать способ транскрипции текста - точных символов по пределу или по ближайшей границе слова.

Это правильно обрабатывает объекты HTML и символы серийного пространства как один (как это просматривается в браузере) - так что ваш пример долженхорошо работают:

$test_str = "<p>
    <span class=\"Underline\"><span class=\"Bold\">Test to be cut</span></span>
</p><p>Some text</p>";

echo cot_string_truncate($test_str, 8);

Результат:

<p>
     <span class="Underline"><span class="Bold">Test to</span></span></p>
...