Тема: Усечение сообщения с сохранением разметки
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=(?:"|"|\'|)(?:.*)|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(' -> #'.$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 на длину тегов.
- косяки в разметке могут сохраниться, т.к. скрипт не добавляет новых тегов
я придерживаюсь золотого правила выпускающего редактора: ошибки в тексте - это не мои ошибки. нефиг их исправлять. автор текста и корректор отвечают за это.