Почему мое регулярное выражение Perl находит только последнее вхождение? - PullRequest
3 голосов
/ 18 марта 2010

У меня есть следующий вход для скрипта Perl, и я хочу получить первое вхождение строк NAME = "..." в каждой из структур <table>...</table>.

Весь файл считывается в одну строку, и регулярное выражение действует на этот вход.

Однако регулярное выражение всегда возвращает последнее вхождение строки NAME="...". Кто-нибудь может объяснить, что происходит и как это можно исправить?

Input file: 
ADSDF
<TABLE>
NAME="ORDERSAA"
line1
line2
NAME="ORDERSA"
line3
NAME="ORDERSAB"
</TABLE>
<TABLE>
line1
line2
NAME="ORDERSB"
line3
</TABLE>
<TABLE>
line1
line2
NAME="ORDERSC"
line3
</TABLE>
<TABLE>
line1
line2
NAME="ORDERSD"
line3
line3
line3
</TABLE>
<TABLE>
line1
line2
NAME="QUOTES2"
line3
NAME="QUOTES3"
NAME="QUOTES4"
line3
NAME="QUOTES5"
line3
</TABLE>
<TABLE>
line1
line2
NAME="QUOTES6"
NAME="QUOTES7"
NAME="QUOTES8"
NAME="QUOTES9"
line3
line3
</TABLE>
<TABLE>
NAME="MyName IsKhan"
</TABLE>

Код Perl начинается здесь:

use warnings;
use strict;

my $nameRegExp = '(<table>((NAME="(.+)")|(.*|\n))*</table>)';

sub extractNames($$){
 my ($ifh, $ofh) = @_;
 my $fullFile;
 read ($ifh, $fullFile, 1024);#Hardcoded to read just 1024 bytes.
 while( $fullFile =~ m#$nameRegExp#gi){
  print "found: ".$4."\n";
 }
}

sub main(){
 if( ($#ARGV + 1 )!= 1){
  die("Usage: extractNames infile\n");
 }
 my $infileName = $ARGV[0];
 my $outfileName = $ARGV[1];
 open my $inFile, "<$infileName" or die("Could not open log file $infileName");
 my $outFile;
 #open my $outFile, ">$outfileName" or die("Could not open log file $outfileName");
 extractNames( $inFile, $outFile );
 close( $inFile );
 #close( $outFile );
}

#call 
main();

Ответы [ 4 ]

4 голосов
/ 18 марта 2010

Попробуйте это:

'(?><TABLE>\n+(?:(?!</TABLE>|NAME=).*\n+)*)NAME="([^"]+)"'

(?:.*\n+)* использует все ненужные строки, в то время как встроенный упреждения - (?!</TABLE>|NAME=) - предотвращает выход за пределы первого поля NAME или конца записи TABLE.,На всякий случай, если есть запись без поля NAME, я обернул большую часть выражения в атомарную группу - (?>...) - для предотвращения бессмысленного возврата.

Обратите внимание, что сейчас существует только одна группа захвата.Полезно использовать их только тогда, когда вам действительно нужно что-то запечатлеть;в противном случае используйте не захватывающий вариант: (?:...).


РЕДАКТИРОВАТЬ: почему ваше регулярное выражение не работает, короткий ответ жадность.После сопоставления с открывающим тэгом эта часть вступает во владение:

((NAME="(.+)")|(.*|\n))*

Часть в самых внешних паренах может соответствовать чему угодно: тегам, NAME= строкам, переводам строк - даже пустым строкам.Оберните это в группу, контролируемую жадным *, и теперь оно соответствует всему .Там нет ничего, что могло бы остановить совпадение в первом поле ИМЯ или даже в конце записи.

Таким образом, он на самом деле "находит" каждое вхождение NAME="..." строк, но делает это в одной попытке сопоставления, которая потребляет весь ввод сразу.На каждой итерации включающей * группы захвата перезаписываются;когда это будет сделано, окончательное значение ИМЯ - MyName IsKhan - это то, что осталось в группе 4.

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

'<TABLE>\n+(?:.*\n+)*?NAME="([^"]+)"'

Простой переход на не жадный квантификатор не помог бы с вашим регулярным выражением;вам также придется внести некоторые структурные изменения.

1 голос
/ 18 марта 2010

Прежде всего, плохая идея - анализировать XML с помощью регулярных выражений. Во-вторых, вам нужно изменить свое регулярное выражение на следующее:

my $nameRegExp = '(<table>((NAME="(.+)?")|(.*?|\n))*?</table>)';

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

1 голос
/ 18 марта 2010
$/ = '</TABLE>';
while (<>) {
    chomp;
    @F = split "\n";
    $g = 0;
    for ($o = 0; $o <= $#F; $o++) {
        if ($F[$o] =~ /^NAME=/) {
            $F[$o] =~ s/^NAME=//g;
            $v = $F[$o];
            $g = 1;
            last;
        }
    }    
    if ($g) {  print $v."\n"; }
}

выход

$ perl myscript.pl file
"ORDERSAA"
"ORDERSB"
"ORDERSC"
"ORDERSD"
"QUOTES2"
"QUOTES6"
"MyName IsKhan"

вся суть: используйте </TABLE> как разделитель записей и символ новой строки как разделитель полей. Пройдите через каждое поле и найдите NAME=. Если найдено, подставьте и получите строку после знака =.

1 голос
/ 18 марта 2010

Попробуйте сделать свое регулярное выражение нежадным:

my $nameRegExp = '(<table>((NAME="(.+?)")|(.*?|\n))*</table>)';

Даже приведенное выше регулярное выражение будет не перечислять все строки NAME в файле. Будет отображаться только одна строка ИМЯ (последняя) из каждого блока <TABLE>...</TABLE>.

Чтобы просмотреть все строки ИМЯ, вы можете сделать:

my $nameRegExp = 'NAME="(.+?)"';

и print $1;

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...