Jump to content
Sign in to follow this  
markimax

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

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: Задача решена, элегантное решение найдено, призовой фонд остался у автора. Уже используется здесь

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

Edited by markimax

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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

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

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

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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

 

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

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

 

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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

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

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

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

Share this post


Link to post
Share on other sites

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

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

1. Исходник

2. Результат

Share this post


Link to post
Share on other sites

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

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

1. Исходник

2. Результат

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

 

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

Текст:

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

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

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

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

Share this post


Link to post
Share on other sites

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

 

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

Share this post


Link to post
Share on other sites

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

 

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

Делаем

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

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

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

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites
Guest
This topic is now closed to further replies.
Sign in to follow this  

  • Similar Content

    • By 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, но хочется найти решение по регулярке.
      Есть у кого-нибудь решение данной задачи? 
    • By 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 тоже применяются только к строке, а не ко всему документу.
  • Recently Browsing   0 members

    No registered users viewing this page.

×

Important Information

On our site, cookies are used and personal data is processed to improve the user interface. To find out what and what personal data we are processing, please go to the link. If you click "I agree," it means that you understand and accept all the conditions specified in this Privacy Notice.