2013-06-07 15 views
5

Chociaż mam wystarczającą znajomość regex w pseudocode, mam problem z tłumaczeniem tego, co chcę zrobić w php regex perl.
Próbuję użyć preg_match, aby wyodrębnić część mojego wyrażenia.
Mam następujący ciąg ${classA.methodA.methodB(classB.methodC(classB.methodD)))} i muszę zrobić 2 rzeczy:
PHP Regex preg_match extract

a. sprawdzić poprawność składni

  • ${classA.methodA.methodB(classB.methodC(classB.methodD)))}ważny
  • ${classA.methodA.methodB}ważny
  • ${classA.methodA.methodB()}nie ważne
  • ${methodB(methodC(classB.methodD)))}nie ważne

b. I trzeba wyodrębnić te informacje ${classA.methodA.methodB(classB.methodC(classB.methodD)))} powinien powrócić

  1. ClassA
  2. metodaA
  3. metodaB (classB.methodC (classB.methodD)))

Utworzyłem ten kod

$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/>'; 
} 

wynikiem jest:
znaleźć
0 $ {myvalue.fdsfs.fsdf.blo (fsdf.fsfds (fsfs.fs))}
1 myvalue
2 fsdf
3 blO (fsdf.fsfds (fsfs.fs))

Oczywiście Mam trudności z wyodrębnieniem powtarzających się metod i nie jest to prawidłowe sprawdzanie poprawności (szczerze mówiąc zostawiłem to na końcu, gdy rozwiążę inny problem), więc puste nawiasy są dozwolone i nie sprawdza, czy po otwarciu nawiasu jest to musi być zamknięty.

Dzięki wszystkim

UPDATE

X m.buettner

dziękuję za pomoc. Zrobiłem szybką próbę twojego kodu, ale daje to bardzo mały problem, chociaż mogę go przekazać. Kwestia jest taka sama od jednej z moich wcześniejszych kodów, że nie mogę pisać tutaj który jest, gdy próbuję ten ciąg:

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

z definicji wzorca to pokazuje:

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

jak można patrz trzecia linia jest catched jako biała przestrzeń, która nie jest obecna. Nie mogłem zrozumieć, dlaczego tak się dzieje, więc czy możesz mi zasugerować, jak mam z nim żyć, z powodu ograniczeń php regex?

To powiedziawszy, mogę ci tylko podziękować. Nie tylko odpowiedziałeś na mój problem, ale także próbowałeś podać jak najwięcej informacji, z wieloma sugestiami na temat prawidłowej ścieżki postępowania przy opracowywaniu wzorców.

Ostatnią rzeczą I (głupi) zapomniał dodać jeden mały ważną sprawę, która jest wiele parametrów podzielonych przecinkami więc

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

musi być ważny.

edytowany do tego

$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 Kazimierz Hippolyte et

Twoja sugestia też jest dobre, ale to wiąże się z małą złożonej sytuacji, gdy za pomocą tego kodu. Chodzi o to, że sam kod jest łatwy do zrozumienia, ale staje się mniej elastyczny. To powiedziało, że dostarczyło mi również wiele informacji, które z pewnością mogą być pomocne w przyszłości.

X Denomales

Dzięki za wsparcie, ale kod spada, gdy próbuję to:

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

wynik jest:

Array 

( [0] => Array ( [0] => $ {classA1.methodA0.methodA1.methodB1 (classB.methodC (cl assB.methodD))} )

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

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

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

) 

Należy

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

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

) 

lub

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

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

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

) 

Odpowiedz

6

To jest trudne. Wzorce rekursywne często przekraczają to, co jest możliwe w przypadku wyrażeń regularnych, a nawet jeśli jest to możliwe, może to prowadzić do bardzo trudnych do wyrażenia, które są bardzo trudne do zrozumienia i utrzymania.

Używasz PHP, a zatem PCRE, który rzeczywiście obsługuje rekurencyjne konstrukcje regex (?n). Ponieważ twój wzór rekurencyjny jest dość regularny, można znaleźć dość praktyczne rozwiązanie za pomocą regex.

Jedno zastrzeżenie, o którym powinienem od razu wspomnieć: ponieważ zezwalasz na dowolną liczbę "pośrednich" wywołań metod na poziom (w Twoim urywku fdsfs i fsdf), nie możesz uzyskać tych wszystkich w osobnych przechwytach. To jest po prostu niemożliwe z PCRE. Każde dopasowanie zawsze da taką samą skończoną liczbę przechwyceń, określoną przez ilość nawiasów otwierających, które zawiera twój wzór. Jeśli grupa przechwytująca jest używana wielokrotnie (np. Przy użyciu czegoś podobnego do ([a-z]+\.)+), to za każdym razem, gdy grupa jest używana, poprzednie przechwycenie zostanie nadpisane, a otrzymasz tylko ostatnią instancję. Dlatego zalecam przechwycenie wszystkich "pośrednich" wywołań metod, a następnie po prostu explode, które będą wynikiem.

Podobnie, nie można (jeśli chcesz) uzyskać przechwyceń wielu poziomów zagnieżdżania na raz. W związku z tym pożądane przechwytywania (gdzie ostatni zawiera wszystkie poziomy zagnieżdżenia) są jedyną opcją - można następnie zastosować wzorzec ponownie do tego ostatniego meczu, aby przejść o kolejny poziom niżej.

Teraz do rzeczywistego wyrażenia:

$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'; 

Kilka ogólnych notatek dla skomplikowanych wyrażeń (aw regex smaków, które je obsługują), zawsze korzystają z wolnego odstępu x modyfikujący, który pozwala na wprowadzenie spacje i komentarze, aby sformatować wyrażenie do swoich pragnień.Bez nich, wzór wygląda następująco:

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

Nawet jeśli masz pisemną regex siebie i jesteś jedyną osobą, która kiedykolwiek pracuje nad projektem - spróbuj zrozumieć to miesiąc od teraz.

Po drugie, nieznacznie uprościłem wzór, używając modyfikatora przypadku-insenstive i. Po prostu usuwa trochę bałaganu, ponieważ możesz pominąć duże litery swoich liter.

Po trzecie, należy pamiętać, że używam klas jednoliterowych, takich jak [$] i [.], aby unikać znaków tam, gdzie jest to możliwe. Jest to po prostu kwestia gustu i możesz korzystać z wariantów odwrotnego ukośnika. Ja osobiście wolę czytelność klas postaci (i wiem, że inni tutaj się nie zgadzają), więc chciałem również przedstawić tę opcję.

Po czwarte, dodałem kotwice dookoła wzoru, aby poza ${...} nie było żadnej nieprawidłowej składni.

Wreszcie, jak działa rekursja? (?n) jest podobna do wstecznej referencji \n, ponieważ odnosi się do przechwytywania grupy n (liczone poprzez otwarcie nawiasów od lewej do prawej). Różnica polega na tym, że odwołanie zwrotne próbuje ponownie dopasować to, co zostało dopasowane przez grupę n, podczas gdy (?n) ponownie stosuje wzorzec. To jest (.)\1 dopasowuje dowolne znaki dwa razy z rzędu, natomiast (.)(?1) dopasowuje dowolny znak, a następnie ponownie stosuje wzór, a więc dopasowuje inny dowolny znak. Jeśli użyjesz jednego z tych konstruktów w grupie n, otrzymasz rekursję. (?0) lub (?R) odnosi się do całego wzoru. To wszystko, co jest magiczne.

Powyższy wzór stosuje się do wejścia

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

spowoduje przechwytuje

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)) 

pamiętać, że istnieją pewne różnice w wyjściach rzeczywiście spodziewanych:

0 jest cały mecz (w tym przypadku ponownie tylko ciąg wejściowy). PHP zawsze to najpierw zgłosi, więc nie możesz się tego pozbyć.

1 to pierwsza grupa przechwytująca, która otacza część rekursywną. Nie potrzebujesz tego na wyjściu, ale niestety nie można odwoływać się do grup niezapisujących, więc potrzebujesz tego również.

2 to nazwa klasy według życzenia.

3 to lista pośrednich nazw metod plus okres końcowy. Używając funkcji explode, można łatwo wyodrębnić z niej wszystkie nazwy metod.

4 to ostateczna nazwa metody, z opcjonalną (rekurencyjną) listą argumentów. Teraz możesz to wziąć i ponownie zastosować wzór, jeśli to konieczne. Zauważ, że dla podejścia całkowicie rekursywnego możesz nieco zmodyfikować wzór.To znaczy: usuń z ${ i } w oddzielnym pierwszym kroku, tak aby cały wzór miał dokładnie taki sam (rekurencyjny) wzór jak końcowe przechwytywanie i możesz użyć (?0) zamiast (?1). Następnie dopasuj, usuń nazwę metody i nawiasy, i powtórz, aż nie uzyskasz więcej nawiasów w ostatnim przechwytywaniu.

Aby uzyskać więcej informacji na temat rekurencji, należy zapoznać się z PHP's PCRE documentation.


Aby zilustrować mój ostatni punkt, o to fragment, który wydobywa wszystkie elementy rekurencyjnie:

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.'; 
    } 
} 

Uwaga ponownie, że nie zaleca się korzystania z wzorca w postaci jednego-liner, ale ta odpowiedź jest już wystarczająco długi.

+0

Kiedy próbuję tego wyrażenia $ expression = '$ {myvalue.fdsfs} "; – user2463968

+0

@ user2463968 Co wtedy? Otrzymasz nazwę klasy, żadnych pośrednich metod i ostateczną metodę bez argumentów. Czy to nie twoja intencja? –

4

można zrobić walidacji i wydobycie z tego samego wzoru, na przykład:

$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:
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

Na końcu wzoru widoczny jest modyfikator x, który umożliwia wstawianie spacji, znaków nowej linii i komentarza wewnątrz wzorca.

Najpierw wzorzec rozpoczyna się definicją nazwanej grupy vn (nazwa zmiennej), w tym miejscu można określić, jak klasa A lub metoda B ma wyglądać dla wszystkich wzorów. Następnie możesz odwołać się do tej definicji we wszystkich wzorach z \g<vn>

Zauważ, że możesz określić, czy chcesz mieć inny typ nazwy dla klas i metodę dodawania innych definicji.Przykład:

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

Wzór sama:

(?<classA>\g<vn>) uchwycenia w podanej grupy Classa o wzorze określonym w VN

samo dla metodaA

metodaB jest inny, ponieważ może zawierać zagnieżdżone nawiasy sis, to jest powód, dla którego używam rekurencyjnego wzoru dla tej części.

Szczegóły:

\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 

można zastąpić go przez \g<vn>(?>\.\g<vn>)+ jeśli chcesz zezwolić na więcej niż jeden sposób.

O zaborczy kwantyfikatorów:

Możesz dodać + po kwantyfikatorem (*+?) w celu uczynienia go zaborczy, zaletą jest to, że silnik regex wiedzieć, że nie trzeba się cofnąć, aby przetestować inne sposoby aby dopasować się do podtytułu. Regex jest wtedy bardziej wydajny.

+0

beat mnie do niego przez 27 sekund: D +1 dla wymienionych grup przechwytywania. Powinienem też zrobić ten nawyk: –

+0

@ m.buettner: Myślę, że zrobiliśmy historyczny temat! –

+0

Twoja sugestia również jest dobra, ale wymaga trochę skomplikowanej sytuacji podczas używania tego kodu. Chodzi o to, że sam kod jest łatwy do zrozumienia, ale staje się mniej elastyczny. To powiedziało, że dostarczyło mi również wiele informacji, które z pewnością mogą być pomocne w przyszłości. – user2463968

2

Opis

Wyrażenie to pasuje i uchwycić tylko ${classA.methodA.methodB(classB.methodC(classB.methodD)))} lub ${classA.methodA.methodB} formaty.

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

enter image description here

Grupy

Grupa 0 dostaje cały mecz z znakiem startu dolara do blisko falowane wspornika

  1. dostaje Class
  2. Pobiera pierwszy metoda
  3. uzyskuje drugą metodę, po której następuje cały tekst do góry, ale nie wliczając do tego zwężającego się nawiasu klamrowego. Jeżeli grupa ma otwarte okrągłe nawiasy pusty () czym pojedyncza zawiedzie

PHP Przykład Kod:

<?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))) 
     ) 

) 

Zastrzeżenia

  • I dodaje numer na końcu klasy i nazwy metod, aby pomóc w zilustrowaniu tego, co dzieje się w grupach.
  • Przykładowy tekst podany w PO nie zawiera zrównoważonych nawiasów okrągłych.
  • Chociaż () będą niedozwolone (()) będą mogły