LINUX.ORG.RU

Регулярные выражения и сложная логика

 , , ,


1

1

Прошу помощи в обработке большого текста регулярными выражениями. Заранее прошу прощения за рекламу программы (тем более виндовой), но как я успел выяснить, в ней синтаксис регэкспов несколько отличается от того, к которому я привык в sed. Работаю в Notepad++ с большим текстовым файлом (>56 000 строк). У файла сложный синтаксис и ему требуется шаблонная обработка. В конце работы файл будет передан на обработку другой программе с довольно уродливым движком разбора текста (собственно, вот из-за чего весь этот сыр-бор). Особенность движка в том, что он не может читать за раз более 47-ми символов текста в одной подстроке (которая заключена в кавычки), но «понимает» свой собственный стиль «конца строки» («@N» - аналог «/n»). До и после кавычек могут быть другие слова, которые трогать не надо (обработке подлежит только тот текст, который заключён в двойные кавычки), и плюс к этому - в одной строке МОГУТ БЫТЬ две и более фразы, заключённые в кавычки. Итого потребуется обработка по такому принципу:

а) выбираем обрабатываемые подстроки (то, что заключается в обычные двойные кавычки).

б) каждая такая подстрока обрабатывается:

1) От начала строки до 47-го символа:

2) если нет «@N», то вставить его на границу слова так, чтобы от начала до «@N» было не более 46-ти символов,

3) иначе (если «@N» встратится ранее, среди первых 46-47 символов) считать следующие 47 символов от последней «@N» до появления двойной кавычки (конец подстроки).

4) если строка не подлежит обработке (менее 47-ми символов или «@N» стоят в достаточном количестве и на нормальных местах) - ничего не делать и братьс за другую подстроку.

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

★★

Ответ на: комментарий от anonymous

Ну почему же есть sed и для windows, без всякого cygwin.

kostik87 ★★★★★ ()
perl -p -i.bak -e 's|"([^"]+)"|q["].(join q[@N], grep { length($_) > 0 } map {my $str=$_; map { substr $str, $_*46, 46 } (0..int length($str) / 46)} split q[@N],$1).q["]|eg' <file>

Или можно оформить как файл (благо, Perl сам читает шебанг):

#!perl -p -i.bak
s|
 "([^"]+)" # строка внутри кавычек
|
 q["]. # кавычки
 (join q[@N], # соединяем обратно
  grep { length($_) > 0 } # фильтруем пустые куски на случай @N как раз на 46-м символе
  map {my $str=$_; map { substr $str, $_*46, 46 } (0..int length($str) / 46)} # затем делим на куски по 46 символов
  split q[@N],$1) # сначала бьём по уже существующим @N, если есть
.q["] # вторая кавычка
|egx;

Запускать как perl file.pl <целевой файл>.

AITap ★★★★★ ()
Ответ на: комментарий от AITap

Большое спасибо за скрипт!

Единственное, что в нём не так: на тестовом варианте этот скрипт проставил «@N» не на границах слов, то есть некторые слова он просто разорвал этой вставкой на две части. Вот пример (Прошу прощения за тарабарщину):

1 lkdsfghj "sfg ggw  kuerjkhg lkjhsf lksdf lkhws lkjhweghksdf  khs ,fk kjhdfgjkh lgm"
2 .kdfs v "dkhjgf ksdfjhkh lkj skj slkjlklkskj lkj kjl lkjhfg"
3 "sfd,hjsdfjh ,mdsgjhsdfkjhsdf, ,jhsjhhfjkskjhf jkhsadfkjh  jhkjhksadfhkjsa" "mgbsdfgmsdf ,jhsdkjhdfkjh l;kjmwqs ,mhdfwk lkfrhklk dwefrkjs kdfkhl lk lkerlkh fg'; rf"
1 lkdsfghj "sfg ggw  kuerjkhg lkjhsf lksdf lkhws lkjhweghk@Nsdf  khs ,fk kjhdfgjkh lgm"
2 .kdfs v "dkhjgf ksdfjhkh lkj skj slkjlklkskj lkj kjl lk@Njhfg"
3 "sfd,hjsdfjh ,mdsgjhsdfkjhsdf, ,jhsjhhfjkskjhf @Njkhsadfkjh  jhkjhksadfhkjsa" "mgbsdfgmsdf ,jhsdkjhdfkjh l;kjmwqs ,mhdfwk lkf@Nrhklk dwefrkjs kdfkhl lk lkerlkh fg'; rf"
Как видно из строки №1: «lkjhweghksdf» стал «lkjhweghk@Nsdf» (@N не на границе слова), а хотелось бы получить "@Nlkjhweghksdf" или даже «lkhws@N lkjhweghksdf». Хоть и не знаю перла - попробую решить сам не заглядывая сюда (ну надо же мне научиться чему-то новому), но прошу на всякий случай написать правильный вариант, если это не сложно.

P.S. анонимусам идти лесом, так как NPP запускается под WINE и в том что я использую инструменты наиболее подходящие для конкретной работы нет ничего плохого. Как сидел под Unix, так и буду сидеть!

zzdnx ★★ ()
Ответ на: Большое спасибо за скрипт! от zzdnx

Я не рискну оставить это однострочником и продолжу только отформатированное решение:

#!perl -p -i.bak
s|
 (?<=")([^"]+)(?=") # строка внутри кавычек
|
 join q[@N], # соединяем обратно
  map {
   my (@words) = split /(\s+)/; # делим строку на "слова", сохраняя разделители
   my @ans = ("");
   while (defined (my $word = shift @words)) { # перебираем по одному слову/разделителю
    if (length($ans[-1].$word) <= 46) {
     $ans[-1].=$word; # склеиваем 2 куска, если они меньше или равны 46 символам
    } elsif (length $word > 46) {
     push @ans, map { substr $word, $_*46, 46 } (0..int length($word) / 46); # разрезаем длинные слова
    } else {
     push @ans, $word; # если предыдущий кусок текста переполнен, добавляем новый
    }
   }
   @ans; # возвращаем массив кусков текста
  }
   split q[@N], $1; # сначала бьём по уже существующим @N, если есть
|egx;

Проверил на нескольких Ваших кусках данных и на паре своих. Вроде бы, работает.

AITap ★★★★★ ()
Ответ на: комментарий от AITap

Оно работает! Большое спасибо за помощь. С perl я так и не освоился (На данном этапе это слишком сложно), но написал несколько парсеров на SED, что является ужасным кастылём, крайне неудобным в использовании. Буду учиться...

zzdnx ★★ ()
Ответ на: комментарий от AITap

Как же хорошо что я в своё время выбрал не перл (писал это крестясь и чур, чур).

anonymous ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.