Обрезать строку по длине не разрушая html \ bbcode

simpex

Новичок
Обрезать строку по длине не разрушая html \ bbcode

PHP:
function breakLongWords($str, $maxLength, $char){
    $wordEndChars = array(" ", "\n", "\r", "\f", "\v", "\0");
    $count = 0;
    $newStr = "";
    $openTag = false;


 

    for($i=0; $i<strlen($str); $i++){
        $newStr .= $str{$i};
       
        if($str{$i} == "<"){
            $openTag = true;
            continue;
        }
        if(($openTag) && ($str{$i} == ">")){
            $openTag = false;
            continue;
        }
       
        if(!$openTag){
            if(in_array($str{$i}, $wordEndChars)){//If not word ending char
            
                if($i>$maxLength){//if current word max length is reached
                    return $newStr;
                 
                }
            }else{//Else char is word ending, reset word char count
                    
            }
        }
       
    }//End for
    return $newStr;
}

$text2= 'a tset21 gfghjdfhg gfdhughruitg <font color=red>gfgnjfk</font>dfsd fhdsfuheiu <b>fdfn</b> fdsjirhfeiohfiohsd<a href="dasds">dsadsdf</a>';

echo function breakLongWords($text2, 300, $char);
вот эта штука в принципе режет текст, правда у нее проверка отстойная на открыт тег или нет
подходит допустим для тегов

т.е. тег [link] [url]http://site.dom[/url] [/link] она спокойно порежет, без пробелов правда не порежет.




хочется чтобы в превьюхе к новости было не больше 300 символов.
все обыскал, ну нету решений никаких подобных.
 

С.

Продвинутый новичок
Очистить от всех тегов и спокойно обрезать, где надо.
 

fixxxer

К.О.
Партнер клуба
ему не разрушая надо. :)

вообще в такой постановке задача не решаемая.

<table><tr><td>куча текста на мегабайт</td></tr></table>

какой результат здесь "правильный"? ;)

(если тут ответ кажется очевидным - <table><tr><td colspan=2>a</td></tr><td>куча текста на мегабайт</td><td>1</td></tr></table> ;) )

ну а в принципе можно тупо порезать "абы как" и прогнать через tidy =)
 

simpex

Новичок
у меня текст не совсем html настоящий

тейбелов вообще нет
встречается вообще 5- 6 тегов

PHP:
[img]xxxx[/img]

подумал что в принципе можно когда эти теги обрабатываю обводить их в специальные маркеры например <!--START-->
<!--FINISH-->


тогда задача сводится как такой текст с обведенными тегами отрезать, первые 300 символов,
если рарез на теге, то идем дальше, и на первом пробеле или \n отрезаем
 

basboy

Новичок
simpex
В твоём случае, на мой взгляд, наиболее приемлемо, это для новости организовать автономное поле с коротким содержанием новости.
т.е. есть два варианта, когда новость просто текст, тогда можно резать.
но когда это html, или просто текст с разметками, то наиболее приемлемо вводить доп. поле.
как fixxxer показал, ты никогда не застрахован от случая.
 

fixxxer

К.О.
Партнер клуба
а. ббкод.
ну если предположить что он заведомо валидный
1. отрезаем с позиции N все кроме тэгов, убираем атрибуты если есть (какой-нить -> [URL]) 2. убираем опять же начиная с N все парные откр-закр тэги
 

kernel32

Новичок
Вот. Написал с нуля.
Только прошу: больно не бейте, сделал на скорую руку и особо не тестировал... :)

PHP:
function breakLongWords($str, $maxLength, $endChar){ 
	// Если длина строки меньше $maxLength
	if (strlen($str) <= $maxLength) return $str;
	// Массив информации о тэгах.
	// Для каждого элемента:
	//	array(имя_тэга, шаблон_начала, закрывающий_тэг);
	$tags = array(
		array('b', '\[b\]', '[/b]'),
		array('i', '\[i\]', '[/i]'),
		array('color', '\[color=.*?\]', '[/color]'),
	);
	// Отрезаем строку по вхождению $endChar...
	$str = substr($str, 0, strpos($str, $endChar, $maxLength));
	// Активируем тэги, чтобы потом узнать, какие остались незакрыты
	$newStr = activateBbTags($str);
	// Генерируем паттерн. Получается что-то вроде {\[b\]|\[i\]|\[color=.*?\]}
	$pattern = '';
	foreach ($tags as $c=>$tInfo) {
		$pattern = $pattern.$tInfo[1];
		if ($c<count($tags)-1) $pattern = $pattern . "|";
	}
	$pattern = "{".$pattern."}";
	// Теперь ищем открытые тэги, которые надо закрыть.
	preg_match_all($pattern, $newStr, $openedTags, PREG_PATTERN_ORDER);
	// Ставим элементы массива совпадений в обратном порядке, 
	// чтобы в правильном порядке закрывать открытые тэги
	$openedTags = array_reverse($openedTags[0]);
	// теперь $newStr - отрезанный текст без активированных тэгов.
	$newStr = $str;
	// Ищем, какой тэг открыт, и в конец строки добавляем его закрывающий тэг
	foreach ($openedTags as $oTag) {
		// Ищем в каждом элементе массива $tags
		foreach ($tags as $tInfo) {
			// Если нашли, что надо, закрываем тэг и выходим из этого (внутреннего) цикла.
			// Внешний цикл продолжается...
			if (preg_match('{^'.$tInfo[1].'$}', $oTag)) {
				$newStr = $newStr.$tInfo[2];
				break;
			}
		}
	}
	// Теперь возвращаем результат :) ^_^
	return $newStr; 
}

function activateBbTags($str) {
	$str = preg_replace('{\[b\](.+?)\[/b\]}', '<b>$1</b>', $str);
	$str = preg_replace('{\[i\](.+?)\[/i\]}', '<i>$1</i>', $str);
	$str = preg_replace('{\[color=(.*?)\](.+?)\[/color\]}', '<font color="$1">$2</font>', $str);
	return $str;
}

// ВНИМАНИЕ!! здесь замените [_i_] на нормальный vB-code i (без подчерков), 
// потому что форум неправильно воспринимает...
$text2= 'asdf; [_i_]lkhj [color=red]skl adfj[/color] sdlfk sdafasd [_i_] sdfasdf asdklhfjkashd';
echo activateBbTags(breakLongWords($text2, 20, " "));
-~{}~ 11.01.08 13:09:

правда, если вызвать мою функцию так:
PHP:
echo activateBbTags(breakLongWords($text2, 50, " "));
то в результате получим
// ВНИМАНИЕ!! здесь замените [_i_] на нормальный vB-code i (без подчерков),
// потому что форум неправильно воспринимает...
Код:
asdf; <i>lkhj <font color="red">skl adfj sdlfk sdafasd [_i_]</i></font>[/i]
Но тут уже дело в "жадности" и "ленивости" квантификаторов... Надо бы с этим разобраться.
 

simpex

Новичок
а я тоже сделал функцию

по дефолту все теги закрыты в [tag] [/tag]

function breakline($text, $maxLength){


$wordEndChars = array(" ", "\n", "\r", "\f", "\v", "\0","<br>");
$openingMarker="[TAGG]";
$closingMarker="[/TAGG]";
$openingMarkerLength = strlen($openingMarker);
$closingMarkerLength = strlen($closingMarker);
$position = 0;

// варант один если тегов нет
if(strpos($text, $openingMarker, $position)===false){
return breakString($text,$maxLength);
}
else{
// если теги есть


while (($position = utf8_strpos($text, $openingMarker, $position)) !== false) {
if($position>$maxLength){
$var=breakString($text,$maxLength);
$var=str_replace("[TAGG]", "", $var);
$var=str_replace("[/TAGG]", "", $var);
return $var;
}

$position += $openingMarkerLength;
if (($closingMarkerPosition = utf8_strpos($text, $closingMarker, $position)) !== false) {



if($closingMarkerPosition>$maxLength){
$var=breakString($text,($closingMarkerPosition+3));
$var=str_replace("[TAGG]", "", $var);
$var=str_replace("[/TAGG]", "", $var);
return $var;


}else{
$position = $closingMarkerPosition + $closingMarkerLength;
}




}



// конец перебора
}


}



не знаю оптимально ли, но вроде пашет









} // конец




$var=breakline($text,300)."\n";
echo $var;
 

kernel32

Новичок
Нашёл еще кое-что: http://ru.php.net/preg_match_all
неизвестный аффтар написал функцию, которая закрывает все html-тэги. Не знаю, на сколько она адекватная.
PHP:
/**
 * close all open xhtml tags at the end of the string
 * 
 * @author Milian Wolff <[url]http://milianw.de[/url]>
 * @param string $html
 * @return string
 */
function closetags($html){
  #put all opened tags into an array
  preg_match_all("#<([a-z]+)( .*)?(?!/)>#iU",$html,$result);
  $openedtags=$result[1];

  #put all closed tags into an array
  preg_match_all("#</([a-z]+)>#iU",$html,$result);
  $closedtags=$result[1];
  $len_opened = count($openedtags);
  # all tags are closed
  if(count($closedtags) == $len_opened){
    return $html;
  }
  $openedtags = array_reverse($openedtags);
  # close tags
  for($i=0;$i<$len_opened;$i++) {
    if (!in_array($openedtags[$i],$closedtags)){
      $html .= '</'.$openedtags[$i].'>';
    } else {
      unset($closedtags[array_search($openedtags[$i],$closedtags)]);
    }
  }
  return $html;
}
Посмотрел я, подумал, и понял, что лучше использовать следующий вариант:
PHP:
function conciseStr($str, $maxLength, $endChar){ 
	if (strlen($str) <= $maxLength) return closeTags(activateBbTags($str));
	$str = activateBbTags($str); // Ваша функция, которая активирует нужные бб-тэги
	$str = substr($str, 0, strpos($str, $endChar, $maxLength));
	$str = closeTags($str); // Эта функция выше
	return $str;
}

$text2= 'asdf; [i_tag]lkhj [color_tag=red]skl adfj[/color_tag] sdlfk sdafasd [/i_tag] sdfa sdf asdklhfjkashd';
echo conciseStr($text2, 50, " ");
Хотя в некоторых случаях и так не пойдёт... Но по крайней мере, если супер-пупер навороченных тэгов нет, то этот вариант сойдёт.
 

kruglov

Новичок
И что делать с
?
Второй вопрос, конечно, нафига в анонсах картинки.
 

kernel32

Новичок
kruglov
Хотя в некоторых случаях и так не пойдёт...
Вот именно с этим и может быть загвоздка. Еще можно придумать свои вариации bb-тэгов, на которые приведённый выше способ пойдёт. К примеру,
Код:
[url title="здесь может быть текст с пробелами" href="адрес"]текст[/url]
Здесь есть вероятность, что текст, содержащий такой тэг, будет обрезан неправильно.
Так что можно, конечно, придумать что-нибудь и посложнее...

А вообще, можно в текст новости вставлять метки, как писали выше. И в анонс вытягивать текст между этими метками.
 

Гриша К.

Новичок
simpex, попробуйте функцию http://phpclub.ru/talk/showthread.php?postid=770772#post770772
Только все символы "<" и ">" в функции замените на "[" и "]". Функцию тестировал, внутри html тэга не разделяет, пробелы не заменяет.
Ну и изменяя регулярное выражение, можно сделать так, чтобы например текст в между открытым и закрытым Html тэгом не изменялся тоже.
 

WP

^_^
Ну что, все варианты исчерпались? ;) Уже можно написать Кошерное Решение?
 
Сверху