Pregunta PHP Regex preg_match extracción


Aunque tengo suficiente conocimiento de expresiones regulares en pseudocódigo, tengo problemas para traducir lo que quiero hacer en php regex perl.
Estoy tratando de usar preg_match para extraer parte de mi expresión.
Tengo la siguiente cadena ${classA.methodA.methodB(classB.methodC(classB.methodD)))} y necesito hacer 2 cosas:

a. validar la sintaxis

  • ${classA.methodA.methodB(classB.methodC(classB.methodD)))}  válido
  • ${classA.methodA.methodB}  válido
  • ${classA.methodA.methodB()}  no es válido
  • ${methodB(methodC(classB.methodD)))}  no es válido

segundo. Necesito extraer esa información ${classA.methodA.methodB(classB.methodC(classB.methodD)))} debería regresar

1. clase A
2. methodA
3. methodB (classB.methodC (classB.methodD)))

He creado este código

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

El resultado es :
encontró
0 $ {myvalue.fdsfs.fsdf.blo (fsdf.fsfds (fsfs.fs))}
1 mi valor
2 fsdf
3 blo (fsdf.fsfds (fsfs.fs))

Obviamente me resulta difícil extraer métodos repetitivos y no lo estoy validando correctamente (sinceramente lo dejé por última vez que resuelva el otro problema) así que se permiten paréntesis vacíos y no se está verificando si se abre un paréntesis una vez debe estar cerrado

Gracias a todos

ACTUALIZAR

X m.buettner

Gracias por tu ayuda. Hice un intento rápido de su código pero da un problema muy pequeño, aunque puedo pasarlo por alto. El problema es el mismo de uno de mis códigos anteriores que no publiqué aquí, que es cuando intento esta cadena:

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

Con su definición de patrón se muestra:

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

Como puede ver, la tercera línea queda atrapada como un espacio en blanco que no está presente. No pude entender por qué estaba haciendo eso, así que, ¿puedes sugerirme cómo o tengo que vivir con eso debido a los límites de expresiones regulares de php?

Dicho eso, solo puedo decirte gracias. No solo respondiste a mi problema, sino que también trataste de ingresar la mayor cantidad de información posible con muchas sugerencias sobre el camino correcto a seguir al desarrollar patrones.

Una última cosa que (estúpido) olvidé agregar un caso pequeño e importante que es múltiples parámetros divididos por una coma, por lo que

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

Debe ser válido.

Edité a esto

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

Tu sugerencia también es buena, pero implica una pequeña situación compleja al usar este código. Quiero decir que el código en sí es fácil de entender pero se vuelve menos flexible. Dicho esto, también me dio mucha información que seguramente puede ser útil en el futuro.

X Denomales

Gracias por su apoyo, pero su código cae cuando intento esto:

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

el resultado es :

Array

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

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

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

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

)

Debería ser

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

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

)

o

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

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

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

)

5
2018-06-07 15:23


origen


Respuestas:


Esta es una pregunta difícil. Los patrones recursivos a menudo están más allá de lo que es posible con las expresiones regulares e incluso si es posible, puede llevar a expresiones muy difíciles de entender y mantener.

Está utilizando PHP y, por lo tanto, PCRE, que de hecho es compatible con las construcciones de expresiones regulares recursivas (?n). Como su patrón recursivo es bastante regular, es posible encontrar una solución un tanto práctica utilizando regex.

Una advertencia que debo mencionar de inmediato: ya que permite un número arbitrario de llamadas de método "intermedio" por nivel (en su fragmento) fdsfs y fsdf), no puedes obtener todos estos en capturas separadas. Eso es simplemente imposible con PCRE. Cada coincidencia siempre arrojará el mismo número finito de capturas, determinado por la cantidad de paréntesis de apertura que contiene su patrón. Si un grupo de captura se usa repetidamente (por ejemplo, usando algo como ([a-z]+\.)+) luego, cada vez que se usa el grupo, la captura previa se sobrescribirá y solo obtendrá la última instancia. Por lo tanto, le recomiendo que capture todas las llamadas al método "intermedio", y luego simplemente explode ese resultado

Del mismo modo, no podría (si quisiera) obtener las capturas de varios niveles de anidamiento a la vez. Por lo tanto, sus capturas deseadas (donde la última incluye todos los niveles de anidamiento) son la única opción; luego, puede aplicar el patrón nuevamente a la última coincidencia para ir un nivel más abajo.

Ahora para la expresión real:

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

Algunas notas generales: para expresiones complicadas (y en los tipos de expresiones regulares que lo admiten), use siempre el espacio libre. x modificador que le permite introducir espacios en blanco y comentarios para formatear la expresión según sus deseos. Sin ellos, el patrón se ve así:

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

Incluso si ha escrito la expresión regular usted mismo y es el único que ha trabajado en el proyecto, intente entender esto dentro de un mes.

Segundo, simplifiqué un poco el patrón usando el caso insensible i modificador Simplemente elimina un poco de desorden, porque puede omitir las variantes en mayúsculas de sus letras.

En tercer lugar, tenga en cuenta que uso clases de un solo carácter como [$] y [.] para escapar de los personajes donde esto sea posible. Eso es simplemente una cuestión de gustos, y usted es libre de usar las variantes de barra invertida. Personalmente prefiero la legibilidad de las clases de personajes (y sé que otros aquí están en desacuerdo), así que también quería presentarles esta opción.

En cuarto lugar, he agregado anclas alrededor de su patrón, por lo que no puede haber una sintaxis inválida fuera del ${...}.

Finalmente, ¿cómo funciona la recursión? (?n) es similar a una referencia inversa \n, en lo que se refiere al grupo de captura n (contado al abrir paréntesis de izquierda a derecha). La diferencia es que una retro-referencia intenta hacer coincidir nuevamente lo que fue igualado por el grupo n, mientras que (?n) aplica el patrón nuevamente Es decir (.)\1 coincide con cualquier carácter dos veces seguidas, mientras que (.)(?1) coincide con cualquier carácter y luego aplica el patrón de nuevo, por lo tanto, coincide con otro carácter arbitrario. Si usas uno de esos (?n) construcciones dentro de la nEn el grupo, obtienes recursión. (?0) o (?R) se refiere a todo el patrón. Esa es toda la magia que hay.

El patrón anterior aplicado a la entrada

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

dará como resultado las capturas

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

Tenga en cuenta que hay algunas diferencias con los resultados que esperaba:

0 es la coincidencia completa (y en este caso solo la cadena de entrada nuevamente). PHP siempre informará esto primero, por lo que no puede deshacerse de él.

1Es el primer grupo de captura que encierra la parte recursiva. No necesitas esto en la salida, pero (?n) desafortunadamente no se puede referir a grupos que no capturan, por lo que también necesita esto.

2 es el nombre de la clase como se desea

3 es la lista de nombres de métodos intermedios, más un período final. Utilizando explode Es fácil extraer todos los nombres de métodos de esto.

4 es el nombre del método final, con la lista de argumentos opcional (recursiva). Ahora puedes tomar esto y volver a aplicar el patrón si es necesario. Tenga en cuenta que para un enfoque completamente recursivo es posible que desee modificar el patrón ligeramente. Es decir: quitarse la ${ y } en un primer paso por separado, para que todo el patrón tenga exactamente el mismo patrón (recursivo) que la captura final, y puede usar (?0) en lugar de (?1). Luego haga coincidir, elimine el nombre del método y los paréntesis, y repita, hasta que no obtenga más paréntesis en la última captura.

Para más información sobre la recursión, eche un vistazo a Documentación de PCRE de PHP.


Para ilustrar mi último punto, aquí hay un fragmento que extrae todos los elementos recursivamente:

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

Tenga en cuenta de nuevo que no recomiendo usar el patrón como una sola línea, pero esta respuesta ya es lo suficientemente larga.


6
2018-06-07 17:47



Puedes hacer la validación y extracción con el mismo patrón, ejemplo:

$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";    
}

Explicación de Regex:
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

Al final del patrón puede ver el modificador x que permite espacios, nuevas líneas y comentarios dentro del patrón.

Primero el patrón comienza con la definición de un grupo nombrado. vn (nombre de la variable), aquí puede definir cómo se ve la clase A o el método B para todo el patrón. Entonces puedes referirte a esta definición en todos los patrones con \g<vn>

Tenga en cuenta que puede definir si desea un tipo diferente de nombre para las clases y el método que agrega otras definiciones. Ejemplo:

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

El propio patrón:

(?<classA>\g<vn>)  capturar en el grupo nombrado clase A con el patrón definido en vn 

Lo mismo para métodoA

método B es diferente porque puede contener paréntesis anidados, es la razón por la que uso un patrón recursivo para esta parte.

Detalle:

\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

puedes reemplazarlo por \g<vn>(?>\.\g<vn>)+ Si quieres permitir más de un método. 

Acerca de los cuantificadores posesivos:

Puedes añadir + después de un cuantificador ( *  +  ? Para que sea posesivo, la ventaja es que el motor de expresiones regulares sabe que no tiene que retroceder para probar otras formas de combinar con un subpatrón. La expresión regular es entonces más eficiente.


4
2018-06-07 17:47



Descripción

Esta expresión coincidirá y solo capturará ${classA.methodA.methodB(classB.methodC(classB.methodD)))} o ${classA.methodA.methodB} formatos.

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

enter image description here

Grupos

El grupo 0 obtiene la partida completa desde el signo de dólar de inicio hasta el corchete ondulado de cierre

  1. obtiene la clase
  2. consigue el primer método
  3. obtiene el segundo método seguido de todo el texto hasta que no incluye el corchete ondulado cerrado. Si este grupo tiene paréntesis abiertos que están vacíos. () entonces este partido fracasará

Ejemplo de código 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)))
        )

)

Descargo de responsabilidad

  • Agregué un número al final de la clase y los nombres de los métodos para ayudar a ilustrar lo que está sucediendo en los grupos
  • El texto de muestra proporcionado en el OP no tiene paréntesis redondos abiertos y cerrados equilibrados.
  • A pesar de que () será desautorizado (()) será permitido

2
2018-06-07 17:49