2013-09-09 7 views
2

Я пытаюсь гиперссылки 400 или около того ключевых слов в документе 50000 слов уценки.Perl regex без переменной длины lookbehind?

Это один из нескольких шагов в цепочке сборки «Perl», поэтому было бы идеально также для достижения гиперповерхности в Perl.

У меня есть отдельный файл содержит все ключевые слова, и отображения каждого фрагменту уценки, который он должен быть заменен, например:

keyword::(keyword)[#heading-to-jump-to] 

Приведенный выше пример подразумевает, что там, где «ключевое слово» происходит в source markdown, его следует заменить фрагментом уценки «(ключевое слово)» [# heading-to-jump-to] ».

Игнорирование ключевых слов, которые являются подстроками других ключевых слов, множественных/сингулярных форм и неоднозначных ключевых слов, это достаточно просто. Но, естественно, есть еще два ограничения.

мне нужно соответствовать только примеры ключевых слов, которые являются:

  • Не на линии не считаясь #
  • Не наиболее непосредственно под заголовком, чтобы перейти к

Простой английский смысл из них: не соответствуют ключевым словам в любых заголовках и не заменяйте ключевые слова, которые находятся под заголовком, на который они ссылаются.

Мой скрипт Perl считывает пары $ keyword :: $ link, а затем пары по парам, заменяет их в регулярное выражение и затем ищет/заменяет документ этим регулярным выражением.

Я написал регулярное выражение, которое выполняет сопоставление (для случаев, которые я проверил вручную до сих пор) с использованием регулярного выражения Regex Buddy's JGSoft. Это выглядит следующим образом:

Frog::(Frog)[#the-frog) 
-->  
([Ff]rog'?s?'?)(?=[\.!\?,;: ])(?<!#+ [\w ]*[Ff]rogs?)(?<!#+ the-frog)(?<!#+ the-frog[^#]*) 

Проблема (или, возможно, проблема) с этим его, что он использует переменную lookbacks длины, которые не поддерживаются Perl. Поэтому я не могу даже проверить это регулярное выражение на полном документе, чтобы увидеть, действительно ли это .

Я прочитал кучу других сообщений о том, как работать с обратными ссылками переменной длины, но я не могу понять, насколько это правильно для моего конкретного случая. Может ли какой-либо из постоянных мастеров регулярных выражений помочь с более быстрым регулярным выражением, которое будет выполняться в Perl?

ответ

1

Как я понимаю, ваша программа будет иметь три состояния:

  1. В заголовке.
  2. В абзаце непосредственно после заголовка.
  3. В других пунктах.

Поскольку это примерно является регулярным языком, он может быть проанализирован с помощью регулярных выражений. Но почему мы хотим это сделать, учитывая, что нам потребуется 400 проходов над текстом?

Возможно, было бы проще разбить файл на массив абзацев. Когда мы нажимаем заголовок, мы создаем все ссылки, которые могут указывать на них.Затем в следующем параграфе мы заменяем все ключевые слова, кроме запрещенных. Например:

my %substitutions = ...; 
my $kw_regex = ...; 
my %forbidden; # holds state 

local $/ = ""; # paragraph mode 
while (<>) { 
    if (/^#/) { 
    # it's a headline 
    @forbidden{ slugify($_) } =(); # extract forbidden link(s) 
    } else { 
    # a paragraph 
    s{($kw_regex)}{ 
     my $keyword = $1; 
     my $link = $substitutions{lc $keyword}; 
     exists $forbidden{$link} ? $keyword : "($keyword)[$link]"; 
    }eg; 
    %forbidden =(); # forbidden links only in 1st paragraph after headline 
    } 
    print; 
} 

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

Regexes are awesome, но они не всегда являются адекватным инструментом.

+0

Thanks amon, Три состояния, которые вы перечисляете, могут быть технически корректными, но состояние 2 не имеет значения в моем случае. Важно следующее: это ключевое слово не должно совпадать, если оно встречается в абзаце, прямо под заголовком, к которому должно быть привязано ключевое слово. Не обязательно, чтобы заголовок для ссылки содержал само ключевое слово (хотя это очень часто). Заголовки не обязательно должны быть отделены от следующих парасов пустыми строками (в основном это не так). – Tode

+0

@Tode Я обновил свое сообщение, чтобы отразить, что ссылки, а не ключевые слова, запрещены. Я не расширил свой ответ, чтобы отключить заголовки из абзацев, потому что это не главная проблема в вашем вопросе (его можно решить, только распечатав только заголовок, и «повторить цикл с остальными», который распознается как параграф). – amon

+0

Спасибо @amon. Я думаю, что я могу использовать адаптацию вашего решения, продолжая линию за строкой (потому что может быть несколько парам под заголовком) и только очистка «запрещенной» ссылки каждый раз, когда встречается новая строка заголовка. – Tode

2

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

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

use strict; 
use warnings; 

my @kw = "keyword::(keyword)[#heading-to-jump-to]"; 
my %rep = map { /([^:]+)::(.+)/ } @kw; 
while (<DATA>) { 
    next if /^#/; 
    for my $kw (keys %rep) { 
     s/\b\Q$kw\E\b/$rep{$kw}/ig; 
    } 
} continue { 
    print; 
} 

__DATA__ 
This is a text with keywords. Only the keyword 'keyword' should be replaced. 
# Dont replace keyword when in a comment 

Выход:

This is a text with keywords. Only the (keyword)[#heading-to-jump-to] '(keyword) 
[#heading-to-jump-to]' should be replaced. 
# Dont replace keyword when in a comment 

Объяснение:

  • Создать хэш замены ключевых слов с map заявления, который возвращает список из двух элементов для каждого ключевого слова :: заменяющая строка.
  • С линиями, которые начинаются с #, перейти непосредственно к print
  • Для каждого ключевого слова в хэш, выполнить глобальную /g, без учета регистра /i замену на каждой строке. Используйте границу слова \b, чтобы предотвратить частичные совпадения, и укажите метасимволы с помощью \Q ... \E. Замените хэш-значение для этого ключевого слова.

Как и во всех языках, у этого есть некоторые оговорки и краевые случаи, которые требуют обработки. Например, граница слова заменит foo на foo-bar. Что касается того, как управлять тем, что не заменять под заголовком, сначала нужно сказать мне, как определить заголовок.

Update:

Если я вас правильно понял, что вы имели в виду, пропуская ключевые слова внутри абзацев с их собственным заголовком, это что-то вроде этого:

#heading-to-jump-to 
Here is 'keyword' not replaced 

Посмотрите строку #heading-to-jump-to и удалить keyword из списка заметок.

Вы можете использовать хэш поиска с ключами, являющимися ссылками на заголовки, и объединить это с генерацией первого хэша. Хотя, в этом случае я бы начал беспокоиться о том, что у вас может быть несколько ключевых слов для каждой ссылки, например. оба foo и bar указывают на #foobar, поэтому #foobar следует исключать ключевые слова foo и bar обе.

my %rep; 
my %heading; 

for my $str (@kw) { 
    chomp $str; 
    my ($kw, $rep) = split /::/, $str, 2; # split into 2 fields 
    $rep{$kw} = $rep; 
    my ($heading) = $rep =~ /\[([^]]+)\]/; 
    push @{ $heading{$heading} }, $kw; 
} 

И тогда вместо того, чтобы просто пропустить строку с next, сделать что-то вроде

my @kws = keys %rep; # default list 
while (<DATA>) { 
    if (/^(#.+)/) { # inside heading 
     my %exclude = map { $_ => 1 } @{ $heading{$1} }; 
     @kws = grep { ! $exclude{$_} } @kws; 
    } else { 
     # not in a heading 
     # ... 
    } 
} 

Обратите внимание, что это всего лишь демонстрация принципа и не предназначен в качестве рабочего кода. Как вы можете видеть, сложная часть здесь - это знать, когда нужно сбросить ограниченный список @kws и когда его использовать. Вы должны будете принять эти решения, поскольку я не знаю ваших данных.

+0

Спасибо за подробный ответ TLP, я попробую что-то вроде того, что вы предлагаете. В ответ на то, как идентифицировать заголовки, любая строка, начинающаяся с #, является заголовком в уценке (а не в комментариях). – Tode

+0

Все еще переваривает это, но кажется, что он не удовлетворяет второму ограничению: $ kw не следует сопоставлять, если заголовок, который мы только что пропустили, был частью «rep-to-jump-to» из $ rep. Это должно быть выполнимо ... – Tode

+0

@Tode Вы хотите сказать, что вам нужно проверить строку, начинающуюся с '#', посмотреть, содержит ли она частичную строку внутри каждой замены ключевого слова, например. '# heading-to-jump-to', а затем не выполнять замены внутри? Ну, в этом случае вам понадобится обратный хеш с извлечением ключевого слова '#heading -...'. – TLP