У меня есть perl-скрипт, который выполняет некоторые замены регулярных выражений в текстовом файле, который мне нужно изменить в следующих строках: (a) мне нужно обработать текст как текстовые блоки, а затем, в зависимости от наличия/отсутствия одного строки различные замены должны быть сделаны. (b) Мне нужно добавить текст в конец каждого блока. (это преобразует текст из программы транскрипции в код LaTeX)
Это должны быть две колонки:
Слева то, как выглядит ввод, справа то, чем он должен стать:
ORIGINAL INPUT EXPECTED OUTCOME
# Single line blocks: label to be replaced and \xe added to en
txt@#Name Text text text \ex[exno=\spkr{Name}] \txt Text text text
\xe
nvb@#Name Text text text \ex[exno=\spkr{Name}] \nvb Text text text
\xe
# Multi-line blocks: labels to be replaced and \xe added to end
txt@#Name Text text text \ex[exno=\spkr{Name}] \txt Text text text
fte@#Name Text text text \freetr Text text text
\xe
txt@#Name Text text text \ex[exno=\spkr{Name}] \txt Text text text
SD (0.0) \silence{0.0}
\xe
txt@#Name Text text text \ex[exno=\spkr{Name}] \txt Text text text
tli@#Name Text text text \translit Text text text
fte@#Name Text text text \freetr Text text text
\xe
# Multi-line block that has the mrb@... line (must start with txt):
txt@#Name Text text text \ex[exno=\spkr{Name}] \begingl \glpreamble Text text text //
mrb@#Name Text text text \gla Text text text //
gle@#Name Text text text \glb Text text text //
fte@#Name Text text text \glft Text text text //
SD (0.0) \endgl
\silence{0.0}
\xe
# The tricky thing here is that (a) the labels get replaced differently, the txt line gets two commands, \begingl and \glpreamble, all lines have to end with // and they end with \endgl and \xe. In case there is an SD (silence duration) line then that needs to go between the \endgl and the \xe. (but not all have the SD).
Блоки разделены дополнительной пустой строкой. Первая строка каждого блока начинается с метки txt@...
, nvb@...
или event
, за ней могут следовать или не следовать последующие строки, начинающиеся с других меток. Каждая метка должна быть заменена чем-то другим, здесь это делается с помощью регулярных выражений, как в приведенном ниже примере (плюс некоторые другие замены, это минимально для объяснения). И затем мне нужно отметить конец каждого блока.
Кроме того, мне нужно где-то там иметь одно условие: если блок включает строку, начинающуюся с метки mrb@ (например, шестой блок выше), применяются разные шаблоны замены.
Следующий сценарий — это то, что у меня есть, но он обрабатывает все построчно. Я знаю, что Perl может выполнять блок за блоком, что должно сделать возможным выполнение модификаций, но, к сожалению, мои навыки слишком рудиментарны, чтобы понять это самостоятельно.
#!/usr/bin/perl
use warnings;
use strict;
open my $fh_in, '<', $ARGV[0] or die "No input: $!";
open my $fh_out, '>', $ARGV[1] or die "No output: $!";
print $fh_out "\\begin{myenv}\n\n"; # begins group at beginning of file
while (<$fh_in>)
{
# general replacements for everything except if block includes a "mrb@" line:
s/^txt@#(\S*)\s+(.*)/\\ex[exno=\\spkr{$1}] \\txt $2 /g;
s/^nvb@#(\S*)\s+(.*)/\\ex[exno=\\spkr{$1}] \\txt $2 /g;
s/^tli@#\S*\s+(.*)/\\translit $1 /g;
s/^fte@#\S*\s+(.*)/\\freetr $1 /g;
s/^SD\s*\((\d*)\.(\d*)\)/\\silence{\($1\.$2\)}/g;
# after each block I need to add "\\xe"
# replacements if block includes a "mrb@" line:
s/^txt@#(\S*)\s+(.*)/\\ex[exno=\\spkr{$1}] \\begingl \\glpreamble $2 // /g;
s/^mrb@#\S*\s+(.*)/\\gla $1 // /g; #
s/^gle@#\S*\s+(.*)/\\glb $1 // /g; #
s/^fte@#\S*\s+(.*)/\\glft $1 // /g; #
s/^tli@#\S*\s+(.*)/\\translit $1 // /g; #
s/^fte@#\S*\s+(.*)/\\freetr $1 // /g; #
s/^SD\s*\((\d*)\.(\d*)\)/\\silence{\($1\.$2\)}/g;
# after each block with a "mrb@" line I need to add "\\endgl" and "\\xe"
# if there is a line starting with SD at the end of the block it needs to go between "\\endgl" and "\\xe"
print $fh_out $_;
}
print $fh_out "\\end{myenv}"; # ends group
Любая помощь высоко ценится!
@ShegitBrahm Конечно, подойдет --- это добавит здесь значительную длину ... Я предполагаю, что здесь нет способа получить два столбца, иначе я мог бы добавить их справа от текстового файла ??
правила форматирования гласят: для таблицы вам нужен тег <pre< и ascii (meta.stackexchange.com/questions/1777/…) - так что да, расширение вправо подойдет
@ShegitBrahm Хорошо, я добавил нужный вывод справа от ввода. Надеюсь, понятно, на что я надеюсь, и не слишком сложно... :/
Детали обработки явно сложны; давайте сначала разберемся, как обрабатывать блоки.
Один из способов — идти построчно и накапливать строки для блока, пока не дойдете до пустой строки. Затем вы обрабатываете свой блок, очищаете буфер и продолжаете работу. Например
use warnings;
use strict;
use feature 'say';
sub process_block {
say "Block:"; say "\t$_" for @{$_[0]};
}
my $file = shift // die "Usage: $0 filename\n"; #/
open my $fh, '<', $file or die "Can't open $file: $!";
my @block;
while (<$fh>) {
chomp;
if (not /\S/) {
if (@block) { # the first empty line
process_block(\@block);
@block = ();
}
next;
}
push @block, $_;
}
process_block(\@block) if @block; # last block may have remained
Вызов process_block
после цикла while
не срабатывает для показанного примера, поскольку перед концом файла есть пустые строки, поэтому последний блок обрабатывается внутри цикла. Но нам нужно сделать так, чтобы последний блок обрабатывался и тогда, когда в конце нет пустых строк.
Внутри process_block
теперь вы можете проверить, содержит ли @block
mrb@#Name
, применить другие (очевидно сложные) условия, запустить регулярное выражение и вывести обработанные строки.
Вот пример после пояснений, но без некоторых деталей
use List::Util qw(any); # used to be in List::MoreUtils
sub process_block {
my @block = @{ $_[0] }; # local copy, to not change @block in caller
if ($block[0] =~ /^txt\@/ and any { /^mrb\@/ } @block) {
for (@block) {
s{^txt\@#(\S*)\s+(.*)}
{\\ex[exno=\\spkr{$1}, exnoformat=X] \\begingl \\glpreamble $2 // }g; #/
s{^mrb\@#\S*\s+(.*)}{\\gla $1 // }g;
# etc
}
if ($block[-1] =~ /^\s*SD/) {
my $SD_line = pop @block;
push @block, '\endgl', $SD_line, '\xe';
}
else {
push @block, '\endgl', '\xe';
}
}
else {
for (@block) {
s/^txt\@#(\S*)\s+(.*)/\\ex[exno=\\spkr{$1}, exnoformat=X] \\txt $2 /g;
s/^tli\@#\S*\s+(.*)/\\translit $1 /g;
# etc
}
push @block, '\xe';
}
say for @block;
say "\n"; # two lines to separate blocks
}
Заметка об эффективности.
Этот код обрабатывает каждую строку в блоке по всем заменам регулярных выражений, чтобы найти ту, которая к ней применима. Отличительный шаблон появляется в самом начале, поэтому «неправильные» строки сразу же терпят неудачу, но мы по-прежнему запускаем механизм регулярных выражений для всех проверок для каждой строки.
Это может (или не может) быть проблемой со многими регулярными выражениями или длинными блоками или если это делается часто, и его можно оптимизировать, если он медленный. Поскольку список подстановок всегда один и тот же, мы можем построить хэш с регулярным выражением, включающим отличительное начало шаблона (как диспетчерская таблица). Например
my %repl_non_mrb = (
'txt@' => sub { s/^txt\@#(\S*)\s+(.*)/\\ex[exno=\\spkr{$1}, exnoformat=X] \\txt $2 /g }
'tli@' => sub { s/^tli\@#\S*\s+(.*)/\\translit $1 /g },
...
);
my %repl_mrb = ( ... );
а затем использовать его в соответствии с
# For blocks without 'mrb@'
for (@block) {
# Capture key: up to # for 'txt@' (etc), up to \s for 'SD'. Other cases?
my ($key) = /^(.*?)(?:#|\s)/;
if ($key and exists $repl_non_mrb{$key}) {
$repl_non_mrb{$key}->(); # run the coderef
}
else { say "No processing key (?) for: $_" } # some error?
}
Это явно требует дополнительной (тщательной) работы, хотя есть и другие способы организации этих регулярных выражений. Но реализация этих (фиксированных) замен регулярных выражений, хешированных их отличительными шаблонами, несомненно, улучшит сложность О(н.м.), заключающуюся в постоянном запуске всех регулярных выражений в каждой строке.
Другой способ - это то, о чем вы спрашиваете
I know perl can do block by block
что можно сделать установив $/
переменная. Он устанавливает то, что затем используется в качестве разделителя между входными записями. Если вы установите здесь значение \n\n
, вы получите блок, обслуживаемый для каждого чтения, в строке
open my $fh, '<', $file or die "Can't open $file: $!";
PROCESS_FILE: {
local $/ = "\n\n";
while (my $block = <$fh>) {
chomp $block;
say "|$block|";
}
};
Я поместил это в блок (названный PROCESS_FILE
просто так), чтобы мы могли изменить $/
с помощью местный. Затем его прежнее значение восстанавливается по мере выхода из блока и файлы снова читаются нормально.
Однако я не вижу смысла делать это здесь, поскольку теперь у вас есть блок в скалярной переменной, в то время как то, что вам нужно сделать, кажется линейно-ориентированным. Поэтому я бы рекомендовал первый подход.
Комментарии архив в чате. Пожалуйста, не забудьте обновить ответ любыми идеями, которые могут быть полезны будущим читателям.
Можно ли добавить «вот как должен выглядеть результат» к вашему (подходящему) вводу примера? Итак, пример, который охватывает все ваши правила, чтобы иметь эффект?