Перейти до вмісту
Пошук в
  • Детальніше...
Шукати результати, які ...
Шукати результати в ...

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


Recommended Posts

Задача:
Есть краткое описание с bbcode тегами

Надо: обрезать  описание по определенному количеству символов не трогая теги (т. е сохранив разметку)

 

Если бы задача стояла убрать теги и обрезать описание - всё было бы тривиально просто

$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 (без разметки и тегов)

 

Но надо сделать тоже самое при этом сохранив теги (разметку).

Например: обрезать описание до 4 символов (при этом не трогая теги, т.е. оставить разметку)

Текст:

0[img=image.jpg]12[b]3[/b]456789

Должен быть результат:

0[img=image.jpg]12[b]3[/b]4

Т.е. 01234 вместе с тегами, т.е. тот что внутри тегов не учитывается в "обрезании"

И второй вариант с учетом того что внутри тегов

Помогите составить паттерн для  preg_match_all(), потому что ушло много времени, решить то я её решу, вот только времени жалко, уже много потратил.

На stackoverflow встали в ступор

Хорошая задачка для развития мозгов, да и для ocStore неплохая, например для обрезания  краткого описания товаров в списке, с сохранением разметки

Я вот думаю, можно ли вообще решить данную задачу регулярными выражениями?

 

Кто найдет элегантное решение данной задачи (лучше конечно регулярным выражением) заплачу 790 рублей лицензией на модуль один , и на второй 490 рублей, итого = 1280 рублей, призовой фонд.

 

 

UPD: Задача решена, элегантное решение найдено, призовой фонд остался у автора. Уже используется здесь

Решение задачи здесь

Змінено користувачем markimax
Надіслати
Поділитися на інших сайтах

Сомневаюсь, что одной регуляркой можно обойтись. Хотя опыта у меня мало. 

Посмотрите http://stackoverflow.com/questions/9042975/shortening-text-tweet-like-without-cutting-links-inside может поможет. По сути такая же задача.

Надіслати
Поділитися на інших сайтах

Как бы сделал я (не профи в PHP). Обрезал бы строку, потом удалил/закрыл все незакрытые теги. Либо обрезал бы текст больше (до открытия последнего тега)

Надіслати
Поділитися на інших сайтах

Я вот думаю, можно ли вообще решить данную задачу регулярными выражениями?

Я редко вопросы задаю,  обычно решение нахожу сам, но здесь я перебрал все варианты моих знаний по регулярным выражениям, но так ответа и не нашел ;(

Получается не простая задача, взаимоисключающая результаты:  не учитывать в квантификации текста (количества символов для "обрезания") теги и что в них, но включить их в результат на своих позициях

Надіслати
Поділитися на інших сайтах

Как бы сделал я (не профи в PHP). Обрезал бы строку, потом удалил/закрыл все незакрытые теги. Либо обрезал бы текст больше (до открытия последнего тега)

Надо теги и что в них оставить as is (т.е. разметку не трогаем), а квантификацию сделать за пределами тегов и обрезать согласно квантификации, но в результате теги и что в них должны присутствовать

Надіслати
Поділитися на інших сайтах

Кто найдет элегантное решение данной задачи (лучше конечно регулярным выражением) заплачу 790 рублей лицензией на модуль

А то эта "тривиальная" задача отняла много времени, которого к сожалению мало

Надіслати
Поділитися на інших сайтах

Даже если регулярка и получится, то это будет такой убойный монстр... Т.е. под конкртный pattern еще можно что-то прикрутить, а универсальное - увы... ьольше времени уйдет на тестирование.

Надіслати
Поділитися на інших сайтах

Даже если регулярка и получится, то это будет такой убойный монстр... Т.е. под конкртный pattern еще можно что-то прикрутить, а универсальное - увы... ьольше времени уйдет на тестирование.

 

Ок - тогда рассмотрим элегантное решение другими способами. призовой фонд  790 рублей остается в силе ;)

Если получиться очень элегантное  - добавлю еще 490 рублей лицензией на этот модуль :)

 

Итого призовой фонд - 1280 рублей

Надіслати
Поділитися на інших сайтах

одной регуляркой не обойтись, нужно писать парсер.

к тому же, эти bb-коды наверняка как-то экранирутся?

Как раз с ними легче, все они в [tag]..[/tag]

Кстати для ocStore тоже полезно  обрезать краткое описание товаров оставляя разметку html посимвольно, по словам и по предложеням

Надіслати
Поділитися на інших сайтах

ТЗ не полное :)

Что обрезаем, где, как, в каких  тегах?

1. Исходник

2. Результат

Все же написал в первом посту

 

Обрезать текст до 4 символов (теги (разметка) сохраняются, но после 4-го символа удаляются)

Текст:

0[img=image.jpg]12[b]3[/b]456789

Должен быть результат:

0[img=image.jpg]12[b]3[/b]4

Т.е. 01234 вместе с тегами, т.е. тот что внутри тегов не учитывается в "обрезании", как и сами теги

Надіслати
Поділитися на інших сайтах

Все же написал в первом посту

 

Обрезать текст до 4 символов (теги (разметка) сохраняются, но после 4-го символа удаляются)

Текст:

0[img=image.jpg]12[b]3[/b]456789

Должен быть результат:

0[img=image.jpg]12[b]3[/b]4

Т.е. 01234 вместе с тегами, т.е. тот что внутри тегов не учитывается в "обрезании", как и сами теги

 

Так обычный plaintext так и обрежется. Задам наводящие вопросы для раскрытия темы:

Как должна вести себя обрезка, если символ, по который нужно обрезать находится внутри тега. Т.е. 

0[img=image.jpg]12[b]3

Он должен его обрезать до:

0[img=image.jpg]12 

или все же закрыть тег

0[img=image.jpg]12[b]3[/b]

Еще я так понял, что теги img должны обрабатываться как какой-то объект длинной в один символ. Или весь текст внутри тегов должен обрабатываться как объект длинной в один символ?

Надіслати
Поділитися на інших сайтах

ТЗ неполное, пример неполный

 

Что мы должны считать?

количество символов plaintext

 

Вход:

12[img=image1][b]23[/b][img=image2]все равно обрежется
результат:
12[img=image1][b]23[/b][img=image2]
или

12[img=image1][b]345[/b][img=image2]все равно обрежется
результат:
12[img=image1][b]34[/b][img=image2]
Надіслати
Поділитися на інших сайтах

Так обычный plaintext так и обрежется. Задам наводящие вопросы для раскрытия темы:

Как должна вести себя обрезка, если символ, по который нужно обрезать находится внутри тега. Т.е. 

0[img=image.jpg]12[b]3

Он должен его обрезать до:

0[img=image.jpg]12 

или все же закрыть тег

0[img=image.jpg]12[b]3[/b]

Еще я так понял, что теги img должны обрабатываться как какой-то объект длинной в один символ. Или весь текст внутри тегов должен обрабатываться как объект длинной в один символ?

 

Все что в тегах - сохраняется в result (я описывал задачу)

 

Т е результат будет для обрезания до 4-х символов

0[img=image.jpg]12[b]3[/b]4

     1                                         23               4      (количество)

Надіслати
Поділитися на інших сайтах

Делаем

1. Получаем список всех [tag]...[/tag]

2. получаем позиции тегов и меняем их на уникальные ###

3. replace уникальных комбинаций ### на ''

4. обрезаем полученную строку

6. вставляем в строку в нужные места тега по запомненным позицмям.

Надіслати
Поділитися на інших сайтах

Решение задачи (UTF-8):

<?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)

       
        $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 = 'М23[img]http://site[/img]122[img=http://site]3456789[img=http://site]33 33';
$limit = 5;
$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;
    }
  }
}


echo $source . "<br>";

?>

Меняя в паттерне (.) на (\x20) - будет обрезаться по словам, на (\.) - по количеству предложений

 

P.S. двух функций нет в хелпере utf8.php opencart- а, это utf8_substr_replace() и utf8_preg_match_all().

Почему надо было использовать utf8_preg_match_all()  а не стандартный я могу объяснить кому интересно (намекну, стандартная функция смещение считает в байтах, а в UTF-8 на символ приходиться пара байт, поэтому смещение второго символа кириллицы - это 4  :ugeek: , поэтому и пришлось использовать новую пользовательскую функцию, так что аккуратнее со смещениями в UTF-8 и флага PREG_OFFSET_CAPTURE, грабли еще те).

Надіслати
Поділитися на інших сайтах

Модераторы - поставьте в заголовке темы "Решено" и удалите этот пост :)

Я бы даже зафиксировал тему, так как нервных клеток было потрачено много, как и времени, решений на stackoverflow и т.п форумах нет,  зато на opencartforum есть, а используется часто в разработке.

Надіслати
Поділитися на інших сайтах

Гість
Ця тема закрита для публікації повідомлень.

×
×
  • Створити...

Important Information

На нашому сайті використовуються файли cookie і відбувається обробка деяких персональних даних користувачів, щоб поліпшити користувальницький інтерфейс. Щоб дізнатися для чого і які персональні дані ми обробляємо перейдіть за посиланням . Якщо Ви натиснете «Я даю згоду», це означає, що Ви розумієте і приймаєте всі умови, зазначені в цьому Повідомленні про конфіденційність.