2016-09-16 4 views
8

Нужно совместить пары key = value в произвольном тексте, используя следующие правила.Регулярное выражение для совпадающих строк с отступом

  • ведущая линия имеет структуру:
    • начало с отступом - «два пробела или вкладки» на лугах один раз, например: ( |\t)+
    • + характер и один космический
    • слова VAR или CONST
    • и key и value с использованием символов =

Примеры:

+ VAR somename = somevalue (indented with two spaces) 
     + VAR name3 = indented by one \t 

Следующие регулярное выражение соответствует такие строки:

/^( |\t)+\+\s+(VAR|CONST)\s+(\w+)\s*=\s*(.*)$/ 

Теперь проблема: Синтаксис позволяет продолжение линии, например, когда за вышеприведенной строкой следует строка, которая начинается, по меньшей мере, с одной последовательностью отступов ( |\t) (aka TWO пробелы или одна вкладка) рассматривается как линия продолжения, и весь ее контент (также с ведущими пробелами) должен быть value для ключа в предыдущем линия.

Пример:

+ VAR multi = 3 line value where the continuation lines 
    are indented (starts with two spaces or one tab) 
    and NOT followed by the '+' 

например, регулярное выражение для продолжения линии является

/^( |\t)+([^\+](.*))$/ 

Раствор легко с линии на основе подхода, например, когда я разбил весь текст на строки и обрабатывал его по очереди.

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

while($text =~ m/^( |\t)+\+\s+(VAR|CONST)\s+(\w+)\s*=\s*((.*)$(?=( |\t)+[^\+](.*)$)*)/gm) { 
    ... 
} 

, но я получил:

(?=( |\t)+[^\+](.*)$)* matches null string many times in regex; marked by <-- HERE in m/^( |\t)+\+\s+(VAR|CONST)\s+(\w+)\s*=\s*((.*)$(?=( |\t)+[^\+](.*)$)* <-- HERE)/ at so line 36. 

Боковой вопрос: как использовать многострочные расширенные регулярные выражения, как:

/ 
    ^( |\t)+  # <- space ... :(
    \+\s+ 
    (VAR|CONST) 
    \s+ 
    (\w+) 
    \s*=\s* 
    (.*)$ 
/x 

, когда регулярное выражение должно содержать точно характер SPACE (например, нельзя использовать универсальный \s)?

Если кому-то нужна помощь, вот код, который производит желаемый результат (с использованием линейного подхода), а также нерабочее решение regex-based.

#!/usr/bin/env perl 
use 5.014; 
use warnings; 
use Data::Dumper; 

my $txt = do { local $/; <DATA> }; 

my @matches1 = parse_by_lines($txt // ''); 
mydump('BY LINES', @matches1); 

my @matches2 = parse_by_one_regex($txt // ''); 
mydump('REGEX', @matches2); 

sub parse_by_lines { #produces the wanted output 
    my ($text) = @_; 
    my @match; 
    my $havekey; 
    for my $line (split "\n", $text) { 
     if($line =~ m/^( |\t)+\+\s+(VAR|CONST)\s+(\w+)\s*=\s*(.*)$/) { 
      push @match, { indent => $1, type => $2, key => $3, val => $4 }; 
      $havekey++; 
     } 
     elsif($havekey && $line =~ m/^( |\t)+([^\+](.*))$/) { #continuation line 
      $match[-1]->{val} .= "\n$line"; #prserve the \n in the val 
     } 
     else { 
      $havekey = 0; 
     } 
    } 
    return @match; 
} 


sub parse_by_one_regex { #not working 
    my ($text) = @_; 
    my @match; 
    while($text =~ m/^( |\t)+\+\s+(VAR|CONST)\s+(\w+)\s*=\s*((.*)$(?=( |\t)+[^\+](.*)$)*)/gm) { 
     push @match, { indent => $1, type => $2, key => $3, val => $4 }; 
    } 
    return @match; 
} 

sub mydump { 
    my($label, @match) = @_; 
    say "#### $label ####"; 
    for my $m (@match) { 
     printf "%-6s: [%s]\n", $_, $m->{$_} for (qw(indent type key val)); 
     print "\n"; 
    } 
} 

__DATA__ 
some arbitrary text lines 
or empty lines 

    could be indented 
    and could contain any character 

    + VAR name1 = var indented by two spaces and the first nonspace character is '+' 
line of arbitrary text 
    + VAR name2 = var indented by 2x2 spaces 

    + VAR name3 = var indented by one \t 
    + VAR name4 = the next line with "name5" is not valid. missing the = character, should not be matched 
    + VAR name5 
    + CONST name6 = the type could be VAR or CONST 

    + VAR multi1 = multiline value where the continuation lines 
    are indented (starts with two spaces or one tab) and NOT followed by the '+' 

    + VAR multi1 = multiline value 
    indented 

    + VAR multi1 = multiline value 
    indented ok too 


    + VAR single = this is single line 
    + because this line even if it is indented, the first nonspace character is '+' 

    + VAR multi2 = multiline 
    could be 
    indented 
     any way 
    and any number of times 
    until the first non-indented line 

the following should NOT match 

+ VAR some = sould not be matched, because the line isn't indented 
+ VAR some = sould not be matched, because the line isn't indented at least with TWO spaces or one tab 
    + SOME name = value not matched because the SOME isn't VAR or CONST 

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

while($text =~/
      (?m)   # multiline match 
      ^    # at the start of the line 
      ([ ]{2}|\t)+ # two spaces or tab - at least once 
      \+    # the '+' character 
      \s*    # followed by any number of spaces (e.g. "+VAR" or "+ VAR" are valid) 
      (VAR|CONST)  # the VAR or CONST 
      \s+    # followed at least one space (e.g. the "VAR_" should not matched) 
      (\w+)   # the keyword 
      \s*=\s*   # the '=' surrounded (and consumed) by any number of spaces 
      (    # capture the whole value (as it is) 
        .*      # any string up to end of line 
        (?:      # followed by (non-capturing group) 
          \R    # one line-break 
          ^    # at the start of the line 
          (?>[ ]{2,}|\t+) # atomic group - at least two spaces or at least one tab 
          [^+]   # followed by any character but '+' 
          .*    # any string up the end of line 
        )*    # any number of times (e.g. optionally) 
      ) 
    /xg) { 
      push @match, { indent => $1, type => $2, key => $3, val => $4 }; 
    } 

edit2 И да, на основе регулярных выражений решения на 34% быстрее (по крайней мере, на моем HW).

+0

К сожалению, не видел. – ClasG

+0

Ваши попытки * подсказки *, которые вы хотите ** захватить ** «ключ» и «значение», но ваш вопрос говорит ** совпадение **. Что он? Если это последнее, я считаю, что revo дал вам ответ ниже. Если вы хотите его захватить, это сложнее ... – ClasG

+0

Ahh ... Теперь я вижу, что вы приняли. Ответ на мой Q. :) – ClasG

ответ

4

Regex:

(?m)^(?: +|\t+)\+ *(?:VAR|CONST) *\w+ *=.*(?:\R^(?> +|\t+)[^+\s].*)* 

Live demo

Важной частью является последний кластер:

(?:    # Start of non-capturing group (a) 
    \R    # One line-break 
    ^   # Start of line 
    (?> +|\t+) # At least two spaces or one tab character (possessively) 
    [^+\s]   # Not followed by `+` or a newline character 
    .*    # Up to end of line 
)*     # Repeat it as much as possible - end of non-capturing group (a) 

Ответ на ваш второй вопрос:

Буквенные символы пробела просто IGN ored как значимая часть регулярного выражения, в то время как модификатор x установлен, если вы не заключили его в классы символов [ ] и используете кванторы [ ]{2,} для выражения времени, которое они должны появиться.

/ 
    (?m) 
    ^
    (?: 
     [ ]{2,} 
     | 
     \t+ 
    )\+ 
    [ ]* 
    (?: 
     VAR 
     | 
     CONST 
    ) 
    [ ]*\w+[ ]*=.* 
    (?: 
     \R 
     ^
     (?> 
      [ ]{2,} 
      | 
      \t+ 
     ) 
     [^+\s].* 
    )* 
/x 

Live demo

+0

замечательный! Thanx :) – cajwine

+0

Спасибо за живые демонстрационные ссылки, классный сайт! Ваше первое регулярное выражение работает в основном, но если строка 'строка произвольного текста' удалена, ему нужно небольшое исправление: замените' [^ +] 'на' (?! \ S * \ +) ' –

+0

Спасибо, что заметили , Я изменил группу, не захватывающую группу, в атомную группу, чтобы предотвратить любые возможные обратные пути. @KjetilS. – revo

Смежные вопросы