1 (14.04.2007 22:46 отредактировано artoodetoo)

Тема: Усечение сообщения с сохранением разметки

hcs, раз ты так считаешь, пусть будет здесь. вытаскиваю тему из ЛС:

Задача: нужна функция обрезания произвольного сообщения с bbcodes, сохраняющая пары открывающих-закрывающих тегов.
Область применения: выдача поиска, различные выборки типа "сообщения за неделю" и т.д. везде, где хочется видеть не только заголовки, но краткое содержание.
Варианты реализации:
a) подавать на вход уже обрезанный текст. функция должна дополнить незакрытые теги.
b) подавать на вход полный текст. функция вырежет все лишнее и оставит все необходимое

Лично я склоняюсь к варианту (b). hcs подкинул работающий паттерн для резки текста и заодно сделал свой вариант (a)

Вот мой тестовый скрипт testcut4.php: (ВАЖНО: в тегах [ code] и [/ code] вставлены пробелы! удалите их перед использованием)

<?php

// debug output. comment echo to suppress all boring output
function decho($str)
{
//    echo $str;
}

// care about magic quotes
function unescape($str)
{
    return (get_magic_quotes_gpc() == 1) ? stripslashes($str) : $str;
}

//
//  Get short version of message. It care about marking.
//  parameters:
//    string  $message     [in, out] - text to cut
//    integer $max_length  [in]      - length to cut
//  returns TRUE when was cut
//
function smartcut(&$message, $max_length=50)
{
    $pattern = '#((?:\[|\[/)(?:b|i|u|s|url|url=(?:[^\[]*?)|email|email=(?:[^\[]*?)|mono|size=(?:[0-9]{2})|center|right|quote|quote=(?:&quot;|"|\'|)(?:.*)|img|imgr|imgl|code|color|color=(?:[a-zA-Z]*|\#?[0-9a-fA-F]{6})|listo|list|li)\])#';
    $result = preg_split($pattern, $message, -1, PREG_SPLIT_DELIM_CAPTURE );

    $trunc_at = -1;
    $length = 0;
    $was_code = FALSE;
    for ($i=0; $i < count($result); $i++)
    {
        $clause = $result[$i];

        decho($i.': ');
        if (strlen($clause) == 0)
        {
            decho("(empty)<br />\n");
            continue;
        }

        if ($trunc_at == -1)
        {
            // text was not truncated yet
            if (!preg_match($pattern, $clause))
            {
                decho('text len='.strlen($clause).' ('.($length + strlen($clause)).')');
                if ($length + strlen($clause) >= $max_length)
                {
                    // truncate text
                    $result[$i] = substr($clause, 0, $max_length-$length);
                    $trunc_at = $i;
                    decho(' -- <span style="color: red">TRUNCATION</span>');
                }
                $length += strlen($clause);
            }
            else
            {
                decho('<b>'.$clause.'</b>');
                if      ($clause == '[  code]')  $was_code = TRUE;
                else if ($clause == '[/ code]') $was_code = FALSE;
            }
        }
        else
        {
            // text was truncated
            if (!preg_match($pattern, $clause))
            {
                // it's odd text. cut it off!
                $result[$i] = '';
                decho('text erased');
            }
            else
            {
                decho('<b>'.$clause.'</b>');

                if ($was_code && $clause != '[/ code]')
                    $result[$i] = '';
                // it's tag. is it opening tag?
                else if (!$was_code && ereg('\[([a-z]+)', $clause, $regs) !== FALSE)
                {
                    $result[$i] = '';

                    // find & destroy its pair
                    $clause = '[/'.$regs[1].']';
                    decho(' erased, loking for <b>'.$clause.'</b>');
                    for ($j = $i+1; $j < count($result); $j++)
                        if ($result[$j] == $clause)
                        {
                            $result[$j] = '';
                            decho(' -&gt; #'.$j.' erased');
                            break;
                        }

                }
                // else { do nothing }

                if      ($clause == '[ code]')  $was_code = TRUE;
                else if ($clause == '[/ code]') $was_code = FALSE;
            }
        }
        decho("<br />\n");
    }

    $message = implode('', $result);
    return $trunc_at != -1;
}

if (isset($_POST['req_message']))
    $message = unescape($_POST['req_message']);
else
    $message = "abc def, [b]Lorem ipsum[/b]\n\n[s]\n[ code]\nLook at this [b] something another[/b]\nmissing[/url], odd [/s]\n[/ code]\n\n[u]Just[/u] test[/s]\n\n";

if (isset($_POST['req_length']) && intval($_POST['req_length']) > 0)
    $max_length = intval($_POST['req_length']);
else
    $max_length = 50;


$result = $message;
$was_cut = smartcut($result, $max_length);

?>
<div class="box">
    <form method="post" action="testcut4.php">
        <label>Message:</label><br />
        <textarea name="req_message" rows="20" cols="95" ><?php echo htmlspecialchars($message) ?></textarea>
        <br />
        <label>Length:</label><br />
        <input type="text" name="req_length" value="<?php echo $max_length ?>" >
        <br /><br />
        <input type="submit" value="Test">
    </form>
</div>

<hr />
<?php echo ($was_cut)? 'MESSAGE WAS TRUNCATED': '' ?>
<div class="box">
<textarea name="req_message" rows="20" cols="95" disabled="disabled"><?php echo htmlspecialchars($result) ?></textarea>
</div>

updated 10:42 MSK: оформлено как функция

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

я придерживаюсь золотого правила выпускающего редактора: ошибки в тексте - это не мои ошибки. нефиг их исправлять. автор текста и корректор отвечают за это.

Сайт artoodetoo

Поделиться

2

Re: Усечение сообщения с сохранением разметки

artoodetoo пишет:

я придерживаюсь золотого правила выпускающего редактора: ошибки в тексте - это не мои ошибки. нефиг их исправлять. автор текста и корректор отвечают за это.

ты  это к чему??
Можноли еще усовершенствовать этот пример?
Суть в следующем. Положим, что этот код обслуживает страницу результатов поиска. Искомое выражение может оказаться за границами обрезки. Поэтому, как и предложила Анна Ли, хорошо было бы показывать искомое выражение и какоето количество слов до него и после него.
Таким образом задача несколько усложнилась,  код получает не только длину обрезки, но и стартовую позицию, до которой тоже следует все обрезать.

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

Сайт hcs

Поделиться

3 (14.04.2007 21:15 отредактировано artoodetoo)

Re: Усечение сообщения с сохранением разметки

обрезка ДО - это конечно геморрой. но я вижу решение smile можно добавить антитеговый цикл от 0 до левой границы обрезки. закрывающие теги стирать всегда, а открывающие - только при нахождении пары. вобщем как нынешний цикл по $j только зеркально.

Добавлено спустя     14 минут   56 секунд:
про редактора я написал вот зачем: возмем к примеру рекламный бизнес. с текстом работает куча народу: креатившики, заказчики, корректоры и т.д. потом эту рекламу отдают в тираж и тут обнаруживается опечатка или грамматическая ошибка. редактор даже если увидит ее - оставит как есть. не ему решать что правильно, а что нет. его функции - размещение на странице. иначе будут такие скандалы, что мама не горюй!
наша функция - обрезка. не будем добавлять свои теги.

Добавлено спустя   1 час   35 минут   27 секунд:
hcs, ты то в курсе, а для других напишу - функцию надо оптимизировать.

надо проверять случаи, когда полный текст укладывается в лимит, тогда сразу возвратим исходный текст.
еще один простой случай - preg_split не нашел ни одного тега - тогда надо резать текст за один substr без синтаксического разбора.

Сайт artoodetoo

Поделиться

4

Re: Усечение сообщения с сохранением разметки

свежая вполне рабочая функция обрезки сообщения: punbb-pe.org.ru/viewtopic.php?pid=320#p320
проведена некоторая оптимизация. вживую результат работы можно увидеть на главной странице сайта

todo: добавить обрезку с начала сообщения для применения в "результатах поиска"

Сайт artoodetoo

Поделиться