Регулярные выражения: как обрезать текст по определенному количеству символов, с тегами, не трогая теги?

Задача:
Есть краткое описание с 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);


?>
  • Вопрос задан
  • 4764 просмотра
Решения вопроса 1
kompi
@kompi
nullstack devoops
Одной регуляркой не обойтись, нужен комплексный подход.
<?php
$source = '12[img]http://site[/img]122[img]http://site[/img]3456789[img]http://site[/img]3333';

$limit = 4;
$counter = 0;
$matches = [];

echo $source . PHP_EOL;

preg_match_all('~(?:\[.*\].*\[\/.*\])|(.)~Usu', $source, $matches, PREG_OFFSET_CAPTURE);

foreach($matches[1] as $val) {
  if(is_array($val)) {
    $counter++;
    if($counter == $limit) {
      $source = substr_replace($source, '', $val[1] + 1);
      break;
    }
  }
}

echo $source . PHP_EOL;


PS Я так понимаю, это функционал для автоматического ката :)
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы