markimax

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

Рекомендуемые сообщения

markimax    1 546

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
halfhope    164

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
halfhope    164

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 546

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 546

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 546

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
toporchillo    445

Задача работы с html-тэгами в общем виде не решается регулярными выражениями. http://toster.ru/q/20020

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
chukcha    1 017

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 546

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

 

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

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

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
freelancer    1 418

одной регуляркой не обойтись, нужно писать парсер.
к тому же, эти bb-коды наверняка как-то экранирутся?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 546

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

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
chukcha    1 017

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

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

1. Исходник

2. Результат

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 546

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

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

1. Исходник

2. Результат

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

 

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

Текст:

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

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
halfhope    164

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

 

Обрезать текст до 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 должны обрабатываться как какой-то объект длинной в один символ. Или весь текст внутри тегов должен обрабатываться как объект длинной в один символ?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
chukcha    1 017

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

 

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

количество символов 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]

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 546

Так обычный 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      (количество)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
chukcha    1 017

Делаем

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

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

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 546

Решение задачи (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, грабли еще те).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 546

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
Гость
Эта тема закрыта для публикации ответов.

  • Похожий контент

    • От zebratratata
      Всем привет. Столкнулся с проблемой в регулярках для vqmod. Я пытаюсь в некий файл для определенного if дописать свое условие, но регулярка никак не хочет искать в многострочном режиме.
      if ($module['layout_id'] == $layout_id && $module['position'] == 'content_top' && $module['status']){ $module_data[] = array( 'code' => $extension['code'], 'setting' => $module, 'sort_order' => $module['sort_order'] ); } мне надо дописать обычный else после этого участка. 
      Пишу регулярку которая ищет участок 
      if ($module['layout_id'] и дальше пропускает все символы до } 
      #if\s*\(\s*\$module\['layout_id'\].*?\}# Но она никогда не срабатывает, если в эту же строку дописать "// }", то все сработает. Получается что vqmod не хочет работать в многострочном режиме?
      Ключи ставил для регулярки. не помогает. Пока что решил вопрос вставим код до if, но хочется найти решение по регулярке.
      Есть у кого-нибудь решение данной задачи? 
    • От halfhope
      Доброго времени суток. 
      Кто-нибудь встречался с проблемой поиска текста для замены(search) в vqmod переменной длинны кол-ва строк. Т.е. В данном случае offset не поможет т.к. кол-во строк может быть разным. 
      Объясню для тех кто не понял:
      В файле catalog/controller/product/category.php после кода:
      $this->data['products'][] = array( 'product_id' => $result['product_id'], 'thumb' => $image, 'name' => $result['name'], 'description' => utf8_substr(strip_tags(html_entity_decode($result['description'], ENT_QUOTES, 'UTF-8')), 0, 100) . '..', 'price' => $price, 'special' => $special, 'tax' => $tax, 'rating' => $result['rating'], 'reviews' => sprintf($this->language->get('text_reviews'), (int)$result['reviews']), 'href' => $this->url->link('product/product', 'path=' . $this->request->get['path'] . '&product_id=' . $result['product_id']) ); }  Необходимо добавить текст, но этот код может быть таким:
      $this->data['products'][] = array( 'product_id' => $result['product_id'], 'thumb' => $image, 'name' => $result['name'], 'mimage'=> $this->model_tool_image->resize($result['mimage'], $this->config->get('config_category_manufacturer_image_width'), $this->config->get('config_category_manufacturer_image_height')), 'manufacturer'=> $result['manufacturer'], 'manufacturer_link' => $this->url->link('product/manufacturer/info', 'manufacturer_id=' . $result['manufacturer_id']), 'description' => utf8_substr(strip_tags(html_entity_decode($result['description'], ENT_QUOTES, 'UTF-8')), 0, 100) . '..', 'price' => $price, 'special' => $special, 'tax' => $tax, 'rating' => $result['rating'], 'reviews' => sprintf($this->language->get('text_reviews'), (int)$result['reviews']), 'href' => $this->url->link('product/product', 'path=' . $this->request->get['path'] . '&product_id=' . $result['product_id']) ); } Т.е. offset тут не поможет. Вот такая регулярка сможет выделить именно тот код, который мне необходим, но vqmod обрабатывает данные построчно и preg_match тоже применяются только к строке, а не ко всему документу.
  • Последние посетители   0 пользователей онлайн

    Ни одного зарегистрированного пользователя не просматривает данную страницу