Как извлечь и проанализировать строки в кавычках в Perl? - PullRequest
1 голос
/ 22 декабря 2009

Добрый день.

Содержание моего текстового файла ниже. tmp.txt (файл очень большого размера)

constant fixup private AlarmFileName = <A "C:\\TMP\\ALARM.LOG">  /* A Format */

constant fixup ConfigAlarms = <U1 0>         /*  U1 Format  */

constant fixup ConfigEvents = <U2 0>         /*  U2 Format  */

Мой код разбора ниже. Код не может обработать C:\\TMP\\ALARM.LOG (строка в кавычках) здесь. Я не знаю, как заменить код "s + ([a-zA-Z0-9]) +>" для обработки как строки [a-zA-Z0-9] (0 выше), так и строки с кавычками (" C: \ TMP \ ALARM.LOG "выше).

$source_file = "tmp.txt";
$dest_xml_file = "my.xml";

#Check existance of root directory
open(SOURCE_FILE, "$source_file") || die "Fail to open file $source_file";
open(DEST_XML_FILE, ">$dest_xml_file") || die "Coult not open output file $dest_xml_file";

$x = 0;

print DEST_XML_FILE  "<!-- from tmp.txt-->\n";
while (<SOURCE_FILE>) 
{
    &ConstantParseAndPrint;

}

sub ConstantParseAndPrint
{
 if ($x == 0)
 {

     if(/^\s*(constant)\s*(fixup|\/\*fixup\*\/|)\s*(private|)\s*(\w+)\s+=\s+<([a-zA-Z0-9]+)\s+([a-zA-Z0-9])+>\s*(\/\*\s*(.*?)\s*\*\/|)(\r|\n|\s)/)
                {
                    $name1 = $1;
                    $name2 = $2;
                    $name3 = $3;
                    $name4 = $4;
                    $name5 = $5;
                    $name6 = $6;
                    $name7 = $7;
                    printf DEST_XML_FILE "\t\t$name1";
                    printf DEST_XML_FILE "\t\t$name2";
                    printf DEST_XML_FILE "\t\t$name3";
                    printf DEST_XML_FILE "\t\t$name4";
                    printf DEST_XML_FILE "\t\t$name5";
                    printf DEST_XML_FILE "\t\t$name6";
                    printf DEST_XML_FILE "\t\t$name7";
                    $x = 1;
  }
 }
}

Спасибо за ваш вклад.

** ПРИВЕТ ВСЕМ,

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

СПАСИБО МНОГО. **

Ответы [ 6 ]

10 голосов
/ 22 декабря 2009

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

Разбить каждую строку на правую и левую часть задания.

my($lhs, $rhs) = split m{\s* = \s*}x, $line, 2;

Теперь работать с ними гораздо проще. Вы можете извлечь информацию с левой стороны, просто разделив ее на пустое пространство, чтобы получить все флаги (константа, исправление и т. Д.), И последним словом будет присвоенное имя.

my @flags = split /\s+/, $lhs;
my $name  = pop @flags;

Затем вы можете при желании отфильтровать свои строки по их флагам.

И значение, которое предположительно находится в скобках, можно легко получить. Использование не жадного регулярного выражения гарантирует, что оно правильно обрабатывает что-то вроде foo = <bar> /* comment <stuff> */.

my($value) = $rhs =~ /<(.*?)>/;

Как видно из этого подхода, в нем не нужно угадывать, какие специальные ключевые слова (константа, исправление, личное) могут появиться в файле.

Я понятия не имею, что еще может быть в этом файле, вы не сказали.

3 голосов
/ 22 декабря 2009

В вашем коде есть некоторые серьезные недостатки дизайна. Я не рассмотрел вашу проблему, но я очистил ваш код.

Самое главное, не использовать глобальные переменные. В относительно коротком фрагменте кода вы используете 3 глобальные переменные. Это НАЧАЛО загадочных ошибок, которые невозможно отследить. Это становится еще более серьезной проблемой, поскольку ваш проект со временем становится больше.

Изучите использование Perl :: Critic . Это поможет вам улучшить ваш код.

Вот аннотированная, продезинфицированная версия вашего кода:

# Always use strict and warnings.
# It prevents bugs.
use strict;
use warnings;

my $source_file   = "tmp.txt";
my $dest_xml_file = "my.xml";

# You aren't checking the existence of anyting here:
#Check existance of root directory 
# Is this a TODO item?

# Use 3 argument open with a lexical filehandle.
# Adding $! to your error messages makes them more useful.
open my $source_fh, '<', $source_file
    or die "Fail to open file $source_file - $!";

open( my $dest_fh, '>', $dest_xml_file 
    or die "Coult not open output file $dest_xml_file - $!";

my $x = 0;  # What the heck does this do?  Give it a meaningful name or
            # delete it.

print $dest_fh  "<!-- from tmp.txt-->\n";
while (my $line = <$source_fh>)   
{

    # Don't use global variables.
    # Explicitly pass all data your sub needs.
    # Any values that need to be applied to external 
    # data should be applied by the calling function,
    # from data that is returned.

    $x = ConstantParseAndPrint( $line, $x, $dest_fh );

}

sub ConstantParseAndPrint {
    my $line          = shift;
    my $mystery_value = shift;
    my $fh            = shift;

    if($mystery_value == 0) {

        # qr{} is a handy way to build a regex.
        # using {} instead of // to mark the boundaries helps
        # cut down on the escaping required when your pattern
        # contains the '/' character.

        # Use the x regex modifier to allow whitespace and 
        # comments in your regex.
        # This very is important when you can't avoid using a big, complex regex.

        # But really don't do it this way at all.
        # Do what Schwern says.
        my $line_match = qr{
            ^                      \s*  # Skip leading spaces
            (constant)             \s*  # look for the constant keyword
            (fixup|/\*fixup\*/|)   \s*  # look for the fixup keyword
            (private|)             \s*  # look for the prive keyword
            (\w+)                  \s+  # Get parameter name
            =                      \s+  
            <                           # get bracketed values
            ([a-zA-Z0-9]+)         \s+  # First value 
            ([a-zA-Z0-9])+              # Second value
            >                      \s*
            (/\*\s*(.*?)\s*\*/|)        # Find any trailing comment
            (\r|\n|\s)                  # Trailing whitespace
        }x;


        if( $line =~ /$line_match/ ) {

            # Any time you find yourself making variables
            # with names like $foo1, $foo2, etc, use an array.

            my @names = ( $1, $2, $3, $4, $5, $6, $7 );

            # printf is for printing formatted data.  
            # If you aren't using any format codes, use print.

            # Using an array makes it easy to print all the tokens.
            print $fh "\t\t$_" for @names;

            $mystery_value = 1;

        }
    }

    return $mystery_value;
}

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

2 голосов
/ 23 декабря 2009

Как уже упоминалось, вам нужно некоторую структуру в вашем регулярном выражении. Подтверждая ваш код, я сделал пару предположений

  • Вы не хотите просто распечатать его в формате с разделителями-вкладками
  • Единственная причина для переменной $x состоит в том, что вы печатаете только одну строку. (хотя last в конце цикла работало бы просто отлично.).

Приняв эти вещи, я решил, что, отвечая на ваш вопрос, я бы:

  1. Покажите, как сделать хороший модифицируемый регулярное выражение.
  2. Код очень простых «семантических действий», которые хранят данные и позволяют вам используйте его как угодно.

Кроме того, следует отметить, что я изменил ввод в секцию __DATA__ и вывод ограничен STDERR - с помощью Smart::Comment комментариев, что мне нужно проверить мои структуры.

Сначала преамбула кода.

use strict;   # always in development!
use warnings; # always in development!
use English qw<$LIST_SEPARATOR>; # It's just helpful.
#use re 'debug';
#use Smart::Comments

Обратите внимание на закомментированный use re .... Если вы действительно хотите увидеть, как регулярный выражение будет проанализировано, оно выдаст много информации, которую вы, вероятно, не хочу видеть (но могу пробиться - с небольшим знанием о Тем не менее, регулярный синтаксический анализ. Это закомментировано, потому что это не новичок дружественный, и будет монополизировать вашу продукцию. (Подробнее об этом см. re .)

Также закомментирована строка use Smart::Comments. Я рекомендую это, но вы можно получить, используя Data::Dumper и print Dumper( \%hash ) строки. (См. Smart::Comments.)

Указание выражения

Но на регулярное выражение. Я использовал взорванную форму регулярного выражения, так что части все объясняется (см. perlre ). Нам нужен один буквенно-цифровой символ ИЛИ строка в кавычках (с разрешенными побегами).

Мы также использовали список имен модификаторов, чтобы «язык» мог развиваться.

Следующее регулярное выражение, которое мы создаем в блоке do, или, как мне нравится называть это, «локализация блок ", так что я могу локализовать $LIST_SEPARATOR (он же $"), чтобы быть регулярным выражением символ чередования. ( '|'). Таким образом, когда я включаю список для интерполяции, оно интерполируется как чередование.

Я дам вам время взглянуть на второе регулярное выражение, прежде чем говорить об этом.

# Modifiable list of modifiers
my @mod_names = qw<constant fixup private>;
# Break out the more complex chunks into separate expressions
my $arg2_regex 
    = qr{ \p{IsAlnum}             # accept a single alphanumeric character
        |                         # OR 
          "                       # Starts with a double quote
          (?>                     # -> We just want to group, not capture
                                  # the '?> controls back tracing
              [^\\"\P{IsPrint}]+  # any print character as long as it is not
                                  # a backslash or a double quote
          |   \\"                 # but we will accept a backslash followed by
                                  # a double quote
          |   (\\\\)+             # OR any amount of doubled backslashes
          )*                      # any number of these
          "
        }msx;

my $line_RE 
    = do { local $LIST_SEPARATOR = '|';
           qr{ \A                # the beginning
               \s*               # however much whitespace you need
               # A sequence of modifier names followed by space
               ((?: (?: @mod_names ) \s+ )*)
               ( \p{IsAlnum}+ )  # at least one alphanumeric character
               \s*               # any amount of whitespace
               =                 # an equals sign
               \s*               # any amount of whitespace
               <                 # open angle bracket
                 (\p{IsAlnum}+)  # Alphanumeric identifier
                 \s+             # required whitespace
                 ( $arg2_regex ) # previously specified arg #2 expression
                 [^>]*?
               >                 # close angle bracket
             }msx
             ;   
          }; 

Регулярное выражение просто говорит, что мы хотим, чтобы любое количество распознанных "модификаторов" отделялось пробелом, сопровождаемым буквенно-цифровым идентификатором (я не уверен, почему вы не хочу подчеркнуть; Я не включаю их, в любом случае.)

За ним следует любое количество пробелов и знак равенства. Так как наборы буквенно-цифровых символов, пробелы и знак равенства не пересекаются, нет причин требовать пробелы. На другой стороне знака равенства, значение ограничено угловыми скобками, поэтому я не вижу смысла для требовать пробелы на этой стороне либо. До равенства все, что вы позволили буквенно-цифровые символы и пробелы, а с другой стороны, все это должно быть под углом скобки. Требуемый пробел ничего не дает, а не требует больше отказоустойчивой. Игнорируйте все это и замените * s на +, если вы ожидаете выход машины.

На другой стороне знака равенства нам нужна пара угловых скобок. Пара состоит из буквенно-цифрового аргумента, причем вторым аргументом является ЛИБО один буквенно-цифровой символ (в зависимости от вашей спецификации) ИЛИ строка, которая может содержать экранированные экранирования или кавычки и даже конечная угловая скобка - до тех пор, пока строка не заканчивается.

Хранение данных

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

### $line_RE
my %fixup_map;
while ( my $line = <DATA> ) { 
    ### $line
    my ( $mod_text, $identifier, $first_arg, $second_arg ) 
        = ( $line =~ /$line_RE/ )
        ;
    die 'Did not parse!' unless $identifier;
    $fixup_map{$identifier}
        = { modifiers_for => { map { $_ => 1 } split /\s+/, $mod_text }
          , first_arg     => $first_arg
          , second_arg    => $second_arg
          };

    ### $fixup_map{$identifier} : $fixup_map{$identifier}
}
__DATA__
constant fixup ConfigAlarms  = <U1 0>
constant fixup ConfigAlarms2 = <U1 2>
constant fixup private AlarmFileName = <A "C:\\TMP\\ALARM.LOG">

В конце вы можете увидеть раздел DATA, когда вы находитесь на начальной стадии как вы, кажется, здесь, наиболее удобно обойтись без логики ввода-вывода и использовать встроенная ручка DATA как у меня здесь.

Я собираю модификаторы в хэш, чтобы мои семантические действия могли быть

#...
my $data = $fixup_map{$id};
#...
if ( $data->{modifiers_for}{public} ) {
    #...
}

Мыльница

Однако главная проблема в том, что у вас нет плана. Для второго «аргумента» в угловых скобках у вас есть регулярное выражение, которое указывает только один буквенно-цифровой символ, но вы хотите расширить его, чтобы разрешить экранированные строки. Я должен ожидать, что вы реализуете небольшое подмножество и постепенно хотите расширять его, делая другие вещи. Если вы пренебрегаете хорошим дизайном с самого начала, то реализация полнофункционального «парсера» станет головной болью.

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

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

Если бы анализ был достаточно сложным, я бы указал минимальную грамматику предпросмотра для Parse::RecDescent и придерживался кодирования семантических действий. Это еще одна рекомендация.

1 голос
/ 22 декабря 2009

объединить первым!

$yourstring =~ s,\\,/,g;  # transform '\' into '/'
$yourstring =~ s,/+,/,g;  # transform multiple '/' into one '/'
1 голос
/ 22 декабря 2009

Я намеренно удалил захваты матчей (вы можете добавить их, если хотите):

m{^\s*constant\s+fixup\s+(?:private\s+)?\w+\s*=\s*<[^>]+>(?:\s*/\*(?:\s*\w*)+\*/)?$};
1 голос
/ 22 декабря 2009
#!/usr/bin/perl


$source_file = "tmp.txt";
$dest_xml_file = "my.xml";

#Check existance of root directory
open(SOURCE_FILE, "$source_file") || die "Fail to open file $source_file";
open(DEST_XML_FILE, ">$dest_xml_file") || die "Coult not open output file $dest_xml_file";

$x = 0;

print DEST_CS_FILE  "<!-- from tmp.txt-->\n";
while (<SOURCE_FILE>)   
{
    &ConstantParseAndPrint;

}

sub ConstantParseAndPrint
{
    if ($x == 0)
    {

#        if(/^\s*(constant)\s*(fixup|\/\*fixup\*\/|)\s*(private|)\s*(\w+)\s+=\s+<([a-zA-Z0-9]+)\s+([a-zA-Z0-9])+>\s*(\/\*\s*(.*?)\s*\*\/|)(\r|\n|\s)/)
        if(/^\s*(constant)\s*(fixup|\/\*fixup\*\/|)\s*(private|)\s*(\w+)\s+=\s+<([a-zA-Z0-9]+)\s+(["']?)([a-zA-Z0-9.:\\]+)\6>\s*(\/\*\s*(.*?)\s*\*\/|)(\r|\n|\s)/)

                {
                    $name1 = $1;
                    $name2 = $2;
                    $name3 = $3;
                    $name4 = $4;
                    $name5 = $5;
                    $name6 = $7;
                    $name7 = $8;
                    printf DEST_XML_FILE "\t\t$name1";
                    printf DEST_XML_FILE "\t\t$name2";
                    printf DEST_XML_FILE "\t\t$name3";
                    printf DEST_XML_FILE "\t\t$name4";
                    printf DEST_XML_FILE "\t\t$name5";
                    printf DEST_XML_FILE "\t\t$name6";
                    printf DEST_XML_FILE "\t\t$name7\n";
#                    $x = 1;
        }
    }
}



Используйте следующий код разбора:

if(/^\s*(constant)\s*(fixup|\/\*fixup\*\/|)\s*(private|)\s*(\w+)\s+=\s+<([a-zA-Z0-9]+)\s+(["']?)([a-zA-Z0-9.:\\]+)\6>\s*(\/\*\s*(.*?)\s*\*\/|)(\r|\n|\s)/) 

Я добавил обработку одинарных и двойных кавычек. Я использую обратную ссылку для соответствия кавычек. Также я обновил класс персонажа для пути. то есть теперь он включает двоеточие (:), точку (.) и обратную косую черту () вместе с буквенно-цифровыми символами.

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