2013-06-07 3 views
5

Хотя у меня достаточно знаний о регулярном выражении в псевдокоде, мне трудно перевести то, что я хочу сделать в php regex perl.
Я пытаюсь использовать preg_match для извлечения части моего выражения.
У меня есть следующая строка ${classA.methodA.methodB(classB.methodC(classB.methodD)))} и мне нужно сделать 2 вещи:
PHP Regex preg_match extract

а. проверки синтаксиса

  • ${classA.methodA.methodB(classB.methodC(classB.methodD)))}действительный
  • ${classA.methodA.methodB}действует
  • ${classA.methodA.methodB()}не действует
  • ${methodB(methodC(classB.methodD)))}не действует

b. Мне нужно извлечь эти данные ${classA.methodA.methodB(classB.methodC(classB.methodD)))} должен возвращать

  1. CLASSA
  2. Methoda
  3. methodB (classB.methodC (classB.methodD)))

Я создал этот код

$expression = '${myvalue.fdsfs.fsdf.blo(fsdf.fsfds(fsfs.fs))}'; 
$pattern = '/\$\{(?:([a-zA-Z0-9]+)\.)(?:([a-zA-Z\d]+)\.)*([a-zA-Z\d.()]+)\}/'; 
if(preg_match($pattern, $expression, $matches)) 
{ 
    echo 'found'.'<br/>'; 
    for($i = 0; $i < count($matches); $i++) 
     echo $i." ".$matches[$i].'<br/>'; 
} 

результат:
найдено
0 $ {myvalue.fdsfs.fsdf.blo (fsdf.fsfds (fsfs.fs))}
1 MyValue
2 fsdf
3 Blo (fsdf.fsfds (fsfs.fs))

Очевидно Мне сложно извлечь повторяющиеся методы, и он не проверяет его должным образом (честно говоря, я оставил его для последнего раз, когда я решил другую проблему), поэтому допустимы пустые круглые скобки, и он не проверяет, не открывается ли это после того, как открыта скобка должны быть закрыты.

Спасибо всем

UPDATE

X m.buettner

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

$expression = '${myvalue.fdsfs}'; 

с определением шаблона показывает:

found 
0 ${myvalue.fdsfs} 
1 myvalue.fdsfs 
2 myvalue 
3 
4 fdsfs 

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

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

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

$expression = '${classA.methodAA(classB.methodBA(classC.methodCA),classC.methodCB)}'; 
$expression = '${classA.methodAA(classB.methodBA(classC.methodCA),classC.methodCB,classD.mehtodDA)}'; 

должен быть действительным.

Я редактировал к этому

$expressionPattern =    
     '/ 
     ^     # beginning of the string 
     [$][{]    # literal ${ 
     (     # group 1, used for recursion 
      (    # group 2 (class name) 
      [a-z\d]+  # one or more alphanumeric characters 
     )     # end of group 2 (class name) 
      [.]    # literal . 
      (    # group 3 (all intermediate method names) 
      (?:    # non-capturing group that matches a single method name 
       [a-z\d]+  # one or more alphanumeric characters 
       [.]   # literal . 
      )*    # end of method name, repeat 0 or more times 
     )     # end of group 3 (intermediate method names); 
      (    # group 4 (final method name and arguments) 
      [a-z\d]+  # one or or more alphanumeric characters 
      (?:    # non-capturing group for arguments 
       [(]   # literal (
       (?1)   # recursively apply the pattern inside group 1 
       (?:  # non-capturing group for multiple arguments   
        [,]  # literal , 
        (?1)  # recursively apply the pattern inside group 1 on parameters 
       )*   # end of multiple arguments group; repeat 0 or more times 
       [)]   # literal) 
      )?    # end of argument-group; make optional 
     )     # end of group 4 (method name and arguments) 
     )     # end of group 1 (recursion group) 
     [}]     # literal } 
     $     # end of the string 
     /ix'; 

X Казимир и Ипполит

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

X Denomales

Спасибо за вашу поддержку, но ваш код падает, когда я пытаюсь это:

$sourcestring='${classA1.methodA0.methodA1.methodB1(classB.methodC(classB.methodD))}'; 

результат:

Array 

( [0] => Массив ( [0] => $ {classA1.methodA0.methodA1.methodB1 (классB.methodC (cl assB.methodD))} )

[1] => Array 
    (
     [0] => classA1 
    ) 

[2] => Array 
    (
     [0] => methodA0 
    ) 

[3] => Array 
    (
     [0] => methodA1.methodB1(classB.methodC(classB.methodD)) 
    ) 

) 

Это должно быть

[2] => Array 
    (
     [0] => methodA0.methodA1 
    ) 

[3] => Array 
    (
     [0] => methodB1(classB.methodC(classB.methodD)) 
    ) 

) 

или

[2] => Array 
    (
     [0] => methodA0 
    ) 

[3] => Array 
    (
     [0] => methodA1 
    ) 

[4] => Array 
    (
     [0] => methodB1(classB.methodC(classB.methodD)) 
    ) 

) 

ответ

6

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

Вы используете PHP и, следовательно, PCRE, который действительно поддерживает рекурсивные конструкции регулярных выражений (?n). Поскольку ваш рекурсивный шаблон довольно регулярный, можно найти несколько практическое решение, использующее регулярное выражение.

Одно из предостережений Я должен упомянуть сразу: поскольку вы разрешаете и произвольное количество «промежуточных» вызовов метода на уровень (в вашем фрагменте fdsfs и fsdf), вы не можете получить все это в отдельных записях. Это просто невозможно с помощью PCRE. Каждое совпадение всегда дает то же конечное количество захватов, определяемое количеством открываемых круглых скобок, которые содержит ваш шаблон. Если группа захвата используется повторно (например, используя что-то вроде ([a-z]+\.)+), то каждый раз, когда используется группа, предыдущий захват будет перезаписан, и вы получите только последний экземпляр. Поэтому я рекомендую вам объединить все вызовы метода «промежуточный» вместе, а затем просто explode.

Аналогичным образом вы не смогли (если бы захотели) получить захваты нескольких уровней гнездования одновременно. Следовательно, ваш желаемый захват (где последний включает все уровни вложенности) является единственным вариантом - вы можете снова применить шаблон к этому последнему совпадению, чтобы идти дальше вниз.

Теперь для фактического выражения:

$pattern = '/ 
    ^     # beginning of the string 
    [$][{]    # literal ${ 
    (     # group 1, used for recursion 
     (     # group 2 (class name) 
     [a-z\d]+   # one or more alphanumeric characters 
    )     # end of group 2 (class name) 
     [.]     # literal . 
     (     # group 3 (all intermediate method names) 
     (?:    # non-capturing group that matches a single method name 
      [a-z\d]+  # one or more alphanumeric characters 
      [.]    # literal . 
     )*    # end of method name, repeat 0 or more times 
    )     # end of group 3 (intermediate method names); 
     (     # group 4 (final method name and arguments) 
     [a-z\d]+   # one or or more alphanumeric characters 
     (?:    # non-capturing group for arguments 
      [(]    # literal (
      (?1)   # recursively apply the pattern inside group 1 
      [)]    # literal) 
     )?    # end of argument-group; make optional 
    )     # end of group 4 (method name and arguments) 
    )      # end of group 1 (recursion group) 
    [}]     # literal } 
    $      # end of the string 
    /ix'; 

Несколько общих заметок для сложных выражений (и в регулярных выражениях ароматизаторов, которые поддерживают его), всегда используйте штрафной интервал x модификатора, который позволяет вводить пробела и комментарии для форматирования выражения по вашему желанию.Без них картина выглядит следующим образом:

'/^[$][{](([a-z\d]+)[.]((?:[a-z\d]+[.])*)([a-z\d]+(?:[(](?1)[)])?))[}]$/ix' 

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

Во-вторых, я немного упростил шаблон, используя модификатор i с корпусом. Он просто удаляет некоторый беспорядок, потому что вы можете опустить варианты вашего письма в верхнем регистре. Обратите внимание, что я использую классы с одним символом, такие как [$], и [.], чтобы избежать символов, где это возможно. Это просто вопрос вкуса, и вы можете использовать варианты обратной косой черты. Я лично предпочитаю читаемость классов персонажей (и я знаю, что другие здесь не согласны), поэтому я хотел представить вам этот вариант.

В-четвертых, я добавил якоря вокруг вашего шаблона, так что не может быть недопустимого синтаксиса вне ${...}.

И наконец, как работает рекурсия? (?n) похож на обратную ссылку \n, поскольку он относится к группе захвата n (подсчитывается путем открытия скобок слева направо). Разница в том, что backreference пытается снова сопоставить то, что соответствовало группе n, тогда как (?n) снова применяет шаблон. То есть (.)\1 соответствует любым символам дважды в строке, тогда как (.)(?1) соответствует любому символу, а затем снова применяет шаблон, поэтому соответствует другому произвольному символу. Если вы используете одну из тех конструкций (?n) в пределах n-й группы, вы получаете рекурсию. (?0) или (?R) относится ко всему рисунку. Это все волшебство.

Данная схема применяется к входу

'${abc.def.ghi.jkl(mno.pqr(stu.vwx))}' 

приведет к захватам

0 ${abc.def.ghi.jkl(mno.pqr(stu.vwx))} 
1 abc.def.ghi.jkl(mno.pqr(stu.vwx)) 
2 abc 
3 def.ghi. 
4 jkl(mno.pqr(stu.vwx)) 

Обратите внимание, что есть несколько отличий от выходов вы на самом деле ожидали:

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

1 - первая группа захвата, которая охватывает рекурсивную часть. Это не нужно в выводе, но (?n), к сожалению, не может ссылаться на группы, не связанные с захватом, так что вам это тоже нужно.

2 - название класса по желанию.

3 - это список промежуточных имен методов, а также конечный период. Используя explode, легко извлечь все имена методов из этого.

4 - это окончательное имя метода, с необязательным (рекурсивным) списком аргументов. Теперь вы можете принять это и применить шаблон снова, если это необходимо. Обратите внимание, что для полностью рекурсивного подхода вы можете немного изменить шаблон.То есть: отделите ${ и } на отдельном первом шаге, чтобы весь шаблон имел тот же (рекурсивный) шаблон, что и окончательный захват, и вы можете использовать (?0) вместо (?1). Затем сопоставьте, удалите имя метода и круглые скобки и повторите, пока вы не получите больше скобок в последнем захвате.

Для получения дополнительной информации о рекурсии, посмотрите на PHP's PCRE documentation.


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

if(!preg_match('/^[$][{](.*)[}]$/', $expression, $matches)) 
    echo 'Invalid syntax.'; 
else 
    traverseExpression($matches[1]); 

function traverseExpression($expression, $level = 0) { 
    $pattern = '/^(([a-z\d]+)[.]((?:[a-z\d]+[.])*)([a-z\d]+(?:[(](?1)[)])?))$/i'; 
    if(preg_match($pattern, $expression, $matches)) { 
     $indent = str_repeat(" ", 4*$level); 
     echo $indent, "Class name: ", $matches[2], "<br />"; 
     foreach(explode(".", $matches[3], -1) as $method) 
      echo $indent, "Method name: ", $method, "<br />"; 
     $parts = preg_split('/[()]/', $matches[4]); 
     echo $indent, "Method name: ", $parts[0], "<br />"; 
     if(count($parts) > 1) { 
      echo $indent, "With arguments:<br />"; 
      traverseExpression($parts[1], $level+1); 
     } 
    } 
    else 
    { 
     echo 'Invalid syntax.'; 
    } 
} 

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

+0

Когда я пытаюсь использовать это выражение $ '= {$ {myvalue.fdsfs}'; – user2463968

+0

@ user2463968 Тогда что? Вы получаете имя класса, промежуточные методы и окончательный метод без аргументов. Разве это не ваше намерение? –

4

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

$subjects = array(
'${classA.methodA.methodB(classB.methodC(classB.methodD))}', 
'${classA.methodA.methodB}', 
'${classA.methodA.methodB()}', 
'${methodB(methodC(classB.methodD))}', 
'${classA.methodA.methodB(classB.methodC(classB.methodD(classC.methodE)))}', 
'${classA.methodA.methodB(classB.methodC(classB.methodD(classC.methodE())))}' 
); 

$pattern = <<<'LOD' 
~ 
# definitions 
(?(DEFINE)(?<vn>[a-z]\w*+)) 

# pattern 
^\$\{ 
    (?<classA>\g<vn>)\. 
    (?<methodA>\g<vn>)\. 
    (?<methodB> 
     \g<vn> ( 
      \(\g<vn> \. \g<vn> (?-1)?+ \) 
     )?+ 
    ) 
}$ 

~x 
LOD; 

foreach($subjects as $subject) { 
    echo "\n\nsubject: $subject"; 
    if (preg_match($pattern, $subject, $m)) 
     printf("\nclassA: %s\nmethodA: %s\nmethodB: %s", 
      $m['classA'], $m['methodA'], $m['methodB']); 
    else 
     echo "\ninvalid string";  
} 

Regex exp lanation:
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

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

Сначала шаблон начинается с определения именованной группы vn (имя переменной), здесь вы можете определить, как выглядит классA или methodB для всего шаблона. Затем вы можете ссылаться на это определение во всем шаблоне с помощью \g<vn>

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

(?(DEFINE)(?<cn>....)) # for class name 
(?(DEFINE)(?<mn>....)) # for method name 

Сама картина:

(?<classA>\g<vn>) захвата в названной группе CLASSA с шаблоном, определенным в уп

одно и то же для Methoda

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

деталь:

\g<vn>   # the method name (methodB) 
(    # open a capture group 
    \(  # literal opening parenthesis 
    \g<vn> \. \g<vn> # for classB.methodC⑴ 
    (?-1)?+ # refer the last capture group (the actual capture group) 
       # one or zero time (possessive) to allow the recursion stop 
       # when there is no more level of parenthesis 
    \)   # literal closing parenthesis 
)?+   # close the capture group 
       # one or zero time (possessive) 
       # to allow method without parameters 

вы можете заменить его \g<vn>(?>\.\g<vn>)+, если вы хотите разрешить более одного метода.

О притяжательных кванторах:

Вы можете добавить + после квантора (*+?), чтобы сделать его притяжательным, преимущество заключается в том, что движок регулярных выражений знает, что он не должен возвращаться назад, чтобы проверить другие способы для соответствия с подшаблоном. Регулярное выражение тогда более эффективно.

+0

Избили меня к нему на 27 секунд: D +1 для названных групп захвата. Я тоже должен сделать эту привычку –

+0

@ m.buettner: Я думаю, что мы сделали историческую тему! –

+0

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

2

Описание

Это выражение будет соответствовать и захватить только ${classA.methodA.methodB(classB.methodC(classB.methodD)))} или ${classA.methodA.methodB} форматы.

(?:^|\n|\r)[$][{]([^.(}]*)[.]([^.(}]*)[.]([^(}]*(?:[(][^}]+[)])?)[}](?=\n|\r|$)

enter image description here

Группа

Group 0 получает весь матч от знака начала доллара к близко волнистому кронштейну

  1. получает класс
  2. получает первый метод
  3. получает второй метод, за которым следует весь текст, но не включая скользящую скользящую скобку. Если эта группа имеет открытые круглые скобки, которые пусты () то этот матч не получится

PHP код Пример:

<?php 
$sourcestring="${classA1.methodA1.methodB1(classB.methodC(classB.methodD)))} 
${classA2.methodA2.methodB2} 
${classA3.methodA3.methodB3()} 
${methodB4(methodC4(classB4.methodD)))} 
${classA5.methodA5.methodB5(classB.methodC(classB.methodD)))}"; 
preg_match_all('/(?:^|\n|\r)[$][{]([^.(}]*)[.]([^.(}]*)[.]([^(}]*(?:[(][^}]+[)])?)[}](?=\n|\r|$)/im',$sourcestring,$matches); 
echo "<pre>".print_r($matches,true); 
?> 

$matches Array: 
(
    [0] => Array 
     (
      [0] => ${classA1.methodA1.methodB1(classB.methodC(classB.methodD)))} 
      [1] => 
${classA2.methodA2.methodB2} 
      [2] => 
${classA5.methodA5.methodB5(classB.methodC(classB.methodD)))} 
     ) 

    [1] => Array 
     (
      [0] => classA1 
      [1] => classA2 
      [2] => classA5 
     ) 

    [2] => Array 
     (
      [0] => methodA1 
      [1] => methodA2 
      [2] => methodA5 
     ) 

    [3] => Array 
     (
      [0] => methodB1(classB.methodC(classB.methodD))) 
      [1] => methodB2 
      [2] => methodB5(classB.methodC(classB.methodD))) 
     ) 

) 

Disclaimers

  • я добавил номер в конце класса и имена методов, чтобы помочь заблокировать то, что происходит в группах.
  • Образец текста, предоставленный в OP, не имеет сбалансированных открытых и закрытых круглых скобок.
  • Хотя () будет запрещено (()) будет разрешено
Смежные вопросы