Задача:
Есть краткое описание с bbcode.
Надо: обрезать описание по определенному количеству символов, не трогая bbcode теги
Например: обрезать описание до 4 символов (при этом не трогая теги)
Текст: 0[img]
http://site[/img]12[img]http://site[/img]3456789Должен быть результат: 0[img]
http://site[/img]12[img]http://site[/img]3
Т.е. 0123 вместе с тегами
Помогите составить паттерн для preg_match_all()
Попытался таким способом
$text="0[img]http://site[/img]12[img]http://site[/img]3456789";
$pattern = ('/((.?\S{0,4})(?=([img].?[/img])))/isu');
preg_match_all($pattern, $text, $out);
Не получилось
Не простая задача, не учитывать в квантификации текста (количества символов для "обрезания") теги и что в них, но включить их в результат
Хорошая разминка для ума.
По этому вопросу stackoverflow впал в ступор
Я вот думаю, можно ли вообще решить данную задачу регулярными выражениями?
Я редко вопросы задаю, обычно решение нахожу сам, но здесь я перебрал все варианты моих знаний по регулярным выражениям, но так ответа и не нашел ;(
Получается не простая задача, взаимоисключающая результаты: не учитывать в квантификации текста (количества символов для "обрезания") теги и что в них, но включить их в результат на своих позициях
Если бы без тегов то все было бы тривиально просто (к примеру):
$amount = 4;
$text = "0<img src='image.jpg'>12<b>3</b>456789";
$pattern = ('/((.*?)\S){0,' . $amount . '}/isu');
preg_match_all($pattern, strip_tags(html_entity_decode($text, ENT_QUOTES, 'UTF-8')), $out);
$outtext = $out[0][0];
И на выходе: 0123
А с "сохранением" тегов - ступор
Решение: <?php
header('Content-Type: text/html; charset=utf-8');
function utf8_strlen($string) {
return strlen(utf8_decode($string));
}
function utf8_substr($string, $offset, $length = null) {
// generates E_NOTICE
// for PHP4 objects, but not PHP5 objects
$string = (string)$string;
$offset = (int)$offset;
if (!is_null($length)) {
$length = (int)$length;
}
// handle trivial cases
if ($length === 0) {
return '';
}
if ($offset < 0 && $length < 0 && $length < $offset) {
return '';
}
// normalise negative offsets (we could use a tail
// anchored pattern, but they are horribly slow!)
if ($offset < 0) {
$strlen = strlen(utf8_decode($string));
$offset = $strlen + $offset;
if ($offset < 0) {
$offset = 0;
}
}
$Op = '';
$Lp = '';
// establish a pattern for offset, a
// non-captured group equal in length to offset
if ($offset > 0) {
$Ox = (int)($offset / 65535);
$Oy = $offset%65535;
if ($Ox) {
$Op = '(?:.{65535}){' . $Ox . '}';
}
$Op = '^(?:' . $Op . '.{' . $Oy . '})';
} else {
$Op = '^';
}
// establish a pattern for length
if (is_null($length)) {
$Lp = '(.*)$';
} else {
if (!isset($strlen)) {
$strlen = strlen(utf8_decode($string));
}
// another trivial case
if ($offset > $strlen) {
return '';
}
if ($length > 0) {
$length = min($strlen - $offset, $length);
$Lx = (int)($length / 65535);
$Ly = $length % 65535;
// negative length requires a captured group
// of length characters
if ($Lx) {
$Lp = '(?:.{65535}){' . $Lx . '}';
}
$Lp = '(' . $Lp . '.{' . $Ly . '})';
} elseif ($length < 0) {
if ($length < ($offset - $strlen)) {
return '';
}
$Lx = (int)((-$length) / 65535);
$Ly = (-$length)%65535;
// negative length requires ... capture everything
// except a group of -length characters
// anchored at the tail-end of the string
if ($Lx) {
$Lp = '(?:.{65535}){' . $Lx . '}';
}
$Lp = '(.*)(?:' . $Lp . '.{' . $Ly . '})$';
}
}
if (!preg_match( '#' . $Op . $Lp . '#us', $string, $match)) {
return '';
}
return $match[1];
}
function utf8_substr_replace($str, $repl, $start , $length = NULL ) {
preg_match_all('/./us', $str, $ar);
preg_match_all('/./us', $repl, $rar);
if( $length === NULL ) {
$length = utf8_strlen($str);
}
array_splice( $ar[0], $start, $length, $rar[0] );
return join('',$ar[0]);
}
function utf8_preg_match_all(
$ps_pattern,
$ps_subject,
&$pa_matches,
$pn_flags = PREG_PATTERN_ORDER,
$pn_offset = 0,
$ps_encoding = 'UTF-8'
) {
// WARNING! - All this function does is to correct offsets, nothing else:
//(code is independent of PREG_PATTER_ORDER / PREG_SET_ORDER)
// if (is_null($ps_encoding)) $ps_encoding = mb_internal_encoding();
$pn_offset = strlen(utf8_substr($ps_subject, 0, $pn_offset, $ps_encoding));
$ret = preg_match_all($ps_pattern, $ps_subject, $pa_matches, $pn_flags, $pn_offset);
if ($ret && ($pn_flags & PREG_OFFSET_CAPTURE)){
foreach($pa_matches as &$ha_match)
foreach($ha_match as &$ha_match)
if (isset($ha_match[1]))
$ha_match[1] = utf8_strlen(substr($ps_subject, 0, $ha_match[1]), $ps_encoding);
}
return $ret;
}
$source = "[img]http://habrastorage.org/storage2/b9a/95a/057/b9a95a057400846db7433dc766f203e7.png[/img]Сделал траницу отзывы, добавил ссылку в меню на страницу отзывы, но вот нет никакой реализации чтобы показывало сразу рядом количество уже оставленых отзывов, как такое реализовать? ";
//$source = "Возможно ли сделать подписку на отзывы не только для зарегистрированных ";
//$source = 'm23[img]http://site[/img]122[img]http://site[/img]3456789[img]http://site[/img]33 33';
$source ="[img]http://habrastorage.org/storage2/b9a/95a/057/b9a95a057400846db7433dc766f203e7.png[/img] сделал страницу отзывы, добавил ссылку в меню на страницу отзывы, но вот нет никакой реализации чтобы показывало сразу рядом количество уже оставленых отзывов, как такое реализовать? ";
$limit = 6;
$counter = 0;
$matches = array();
echo $source . "<br>";
utf8_preg_match_all('/(?:\[.*\].*\[\/.*\])|(.)/Usiu', $source, $matches, PREG_OFFSET_CAPTURE);
foreach($matches[1] as $num=>$val) {
if(is_array($val)) {
$counter++;
if($counter == $limit) {
$source = utf8_substr_replace($source, '', $val[1] + 1);
break;
}
}
}
?>
<?php
echo $source . "<br>";
//print_r('<PRE>');
//print_r($matches);
?>