Expresiones Regulares

Anuncio
Expresiones Regulares
María Consuelo Franky
[email protected]
Universidad Javeriana - 2010
1
Elementos de las expresiones regulares
Referencias:
tutorial corto: http://www.regular-expressions.info/quickstart.html
tabla resumen: http://www.regular-expressions.info/reference.html
tutorial largo: http://www.regular-expressions.info/tutorial.htm
2
Concepto y tipos de expresiones regulares
Patrones de texto y concordancia :
Una expresión regular (regex) es un patrón describiendo
cierto texto
el objetivo es buscar si una expresión regular ocurre en una
cadena en cuyo caso se dice que hay concordancia
Expresión regular que consiste de un caracter
ejemplo básico:
• expresión regular de un solo carácter: a
• cadena donde se busca la expresión regular: Jack is a boy
• resultado: la expresión regular concuerda con la primera a de la
cadena, y después si se continúa la búsqueda concuerda con la
segunda a
Metacaracteres con significado especial
• Son: [ \ ^ $ . | ? * + (
)
• según el motor también {
• si se quieren usar como caracter normal para buscar, debe
añadirse \ como escape: ej: para buscar el texto 1+2 la
expresión regular es 1\+2
3
Expresión regular que consiste de un conjunto de
caracteres alternativos (Character Set)
Indicar la lista de caracteres alternativos:
• ejemplo de expresión regular con 2 caracteres alternativos [ae]
• expresión regular que contiene un conjunto de caracteres
alternativos: gr[ae]y
• concuerda con cadenas gray o grey
El conjunto de caracteres alternativos puede expresarse
como un rango, ejemplos: [0-9], [0-9a-fA-F]
El conjunto de caracteres alternativos puede negarse con ^
para buscar cadenas que no tengan esos caracteres
• ejemplo: q[^xz] concuerda con qu en la cadena question
Conjuntos de caracteres alternativos especiales:
• \d : significa un solo carácter que es un dígito
• \w : significa un solo carácter alfanumérico o _
• \s : significa un carácter blanco: espacio, tab, y en algunos
motores también incluye cambio de línea
4
Caracteres especiales
Caracteres de control:
•
•
•
•
\t : tab
\r : carriage return
\n : line feed
NOTA: como separador de línea Windows usa \r\n mientras que
Unix usa \n
para independizarse se usa propiedad ${line.separator}
• \a : bell, \e : escape, \f : form feed,, \v : vertical tab
• \xFF por ejemplo \xA9 : código hexadecimal del caracter @
• \uFFFF por ejemplo \u20AC: código unicode del símbolo del
euro)
Carácter punto (dot):
• el . concuerda con cualquier carácter excepto con cambio de
línea
• equivale a [^\n] en Unix o a [^\r\n] en Windows (que
son más precisos)
• ejemplo: la expresión regular gr.y concuerda con cadenas
gray, grey, gr%y, etc
5
Anchors: indican una posición de concordancia
dentro de la cadena analizada
• ^ : comienzo de cadena, por ejemplo la expresión regular ^b
•
•
•
•
concuerda con la primera b de la cadena bob
$ : fin de cadena
\b : límite de una palabra: primero o último caracter
alfanúmerico
\B: concuerda con cualquier posición que no concuerde con \b
\B
\Q...\E concuerda con caracteres entre estos dos
delimitadores suprimiendo el significado de metacaracteres :
ejemplo: \Q+-*/\E concuerda con +-*/
Alternación (OR)
Expresión regular con varias alternativas separadas con |
ejemplo: expresión regular cat|dog concuerda con cat en la
cadena About cats and dogs y luego con dog
6
Cuantificadores de repetición
un token seguido de ? se considera opcional
• ejemplo: expresión regular colou?r concuerda con colour o
color
un token seguido de + se repite 1 o más veces
• ejemplo: expresión 99[A-Za-z0-9]+ concuerda con cadenas
99CaSa1, 991, 99B, 99456 pero no con 99
• [\s]+ representa uno o varios blancos (espacios y/o tabs)
• [^\s]+ representa una palabra (no tiene blancos internos)
• /\*.+\*/ representa un comentario Java en una sola línea
• /\*[^\*/]+\*/ representa un comentario Java multilínea
• varios caracteres diferentes de cambio de línea .+
IMPORTANTE
un token seguido de * se repite 0 o más veces
• ejemplo: expresión <[A-Za-z][A-Za-z0-9]*> concuerda
con cualquier etiqueta HTML sin atributos como <b> <body> ,
<table>, … las cuales deben empezar por <, luego una letra y
luego pueden opcionalmente ir más letras o números y deben
terminar con >
7
se puede indicar el número de repeticiones del token con { }
• ejemplo: expresión \b[1-9][0-9]{3}\b concuerda con
cadena que indica número entre 1000 y 9999
• también se puede indicar un número mínimo y máximo de
repeticiones: {3,5} o un número mínimo {3, }
grupo: varios tokens se pueden agrupar con () para luego
aplicarles un cuantificador de repetición.
• ejemplo: Set(Value)? concuerda con Set o con SetValue
• ejemplo: ab(cd)+ concuerda con abcd , abcdcd , abcdcdcd , …
8
Ejemplos de expresiones regulares
y textos concordantes
9
Ejemplos de Texto en una sola línea
Ejemplo 1
El texto a identificar define la propiedad MIPROPIEDAD
asignándole un valor cualquiera
ilustración:
propiedad1=una casa
propiedad2=bienvenido
MIPROPIEDAD=para salir del sistema
propiedad3=ir a pagina principal
expresión regular para identificar el texto de interés:
MIPROPIEDAD=(.+)
Ejemplo 2
El texto a identificar define cualquier propiedad
expresión regular correspondiente: ([^\s]+)=([^\s]+)
palabra
10
Ejemplos de Textos multínea
Ejemplo 3
El texto a identificar empieza por multi line, seguido de 1 o
varias líneas de texto y termina en una línea que dice fff
ilustración:
alguna linea previa
multi line
la linea1
la linea2
la linea4
fff
mas texto
mas texto
expresión regular para identificar el texto multilínea:
multi line(${line.separator}.*)+fff
en donde ${line.separator} es reemplazado por cambio de
línea según el sistema
11
Ejemplo 4
El texto a identificar empieza por #$$$$$, seguido de otra
linea con la definición de una propiedad cualquiera
ilustración:
linea previa
#$$$$$
property.key3=value3
otras lineas
expresión regular para identificar el texto multilínea:
#\${5}${line.separator}([^\s]+)=([^\s]+)
$$$$$
palabra
palabra
12
Ejemplo 5
El texto a identificar corresponde a un comentario Java,
antecedido de 1 o varias líneas en blanco, seguido de 1 o
varias líneas en blanco, seguido de la palabra package
ilustración:
/* comentario
de varias
lineas
*/
package
linea posterior
expresión regular para identificar el texto multilínea:
([\n]*/\*[^\*/]+\*/[\s]*[\n]*[\s]*)package
líneas en
blanco
(Unix)
comentario Java de
varias líneas
espacios líneas en espacios
blanco
13
Ejemplo 6
El texto a identificar corresponde a una linea que comienza por
@Name seguido de una cadena entre comillas y entre
paréntesis, seguido de 1 o varias líneas en blanco y seguido
de una línea que contiene public
ilustración:
package mipaquete;
@Name("parameterList")
public
private int atributo;
}
Class ParameterList {
expresión regular para identificar el texto multilínea:
(@Name\(${comilla}(.*)${comilla}\))[\r\n\s]*(public)
cadena entre comillas y entre paréntesis
propiedad ${comilla} vale "
líneas en blanco
o con espacios
14
Tareas ANT para trabajar
con expresiones regulares
Referencia: http://ant.apache.org/manual/
(sección: File Tasks)
15
Tareas básicas sobre archivos
mkdir : crea un directorio si no existe, incluyendo
toda su ruta
Ejemplo <mkdir dir="${dist}/lib"/>
uso dentro de un build.xml
<?xml version="1.0"?>
<project name="regular-expressions" default="init" basedir=".">
<!-- external file of properties -->
<property file="build.properties" />
<!-- directories -->
<property name="fuente" value="${basedir}/fuente" />
<property name="destino" value="${basedir}/destino" />
<target name="init" description="Initialize the build">
<mkdir dir="${destino}"/>
</target>
</project>
16
copy : copia un archivo (o archivos) a una
localización destino
Ejemplos:
• copiar renombrando un archivo:
<copy file="myfile.txt" tofile="mycopy.txt"/>
• copiar un archivo a otro directorio:
<copy file="myfile.txt" todir="../some/other/dir"/>
• copiar un conjunto de archivos a otro directorio:
<copy todir="../dest/dir">
<fileset dir="src_dir" excludes="**/*.java"/>
</copy>
17
uso del copy dentro de un build.xml
<?xml version="1.0"?>
<project name="regular-expressions" default="init" basedir=".">
<!-- external file of properties -->
<property file="build.properties" />
<!-- directories -->
<property name="fuente" value="${basedir}/fuente" />
<property name="destino" value="${basedir}/destino" />
<target name="init" description="Initialize the build">
<echo message = "creating directory ${destino}" />
<mkdir dir="${destino}"/>
</target>
<target name="copiar" depends="init"
description="Copy a file">
<copy file="${fuente}/MiClase1.java"
tofile="${destino}/MiClase1.java"/>
</target>
</project>
NOTA: no es obligatorio que exista el directorio destino antes de copiar un archivo
18
move: mueve un archivo o un directorio a un
nuevo destino
Ejemplos:
• renombrar un archivo:
<move file="file.orig" tofile="file.moved"/>
• mover un archivo a un nuevo directorio:
<move file="file.orig" todir="dir/to/move/to"/>
• mover un directorio a otro directorio
<move todir="new/dir/to/move/to">
<fileset dir="src/dir"/>
</move>
• mover un conjunto de archivos a un directorio
<move todir="some/new/dir">
<fileset dir="my/src/dir">
<include name="**/*.jar"/>
<exclude name="**/ant.jar"/>
</fileset>
</move>
19
delete: elimina un archivo o un directorio
Ejemplos:
• eliminar un archivo:
<delete file="/lib/ant.jar"/>
• eliminar un directorio:
<delete dir="lib"/>
• eliminar los archivos.bak del directorio actual:
<delete>
<fileset dir="." includes="**/*.bak"/>
</delete>
20
Tareas de reemplazo de texto en archivos
replace : reemplaza una cadena por otra en un
archivo (o en los archivos de un directorio)
Ejemplo: reemplazar todas las ocurrencias de la cadena
com.acme.proy1 por org.acme.proy2 en el archivo
indicado:
<replace file="${src}/MiClase.java"
token="com.acme.proy1" value="org.acme.proy2"/>
replace con replacefilterfile: reemplaza en un
archivo cadenas por valores de acuerdo a archivo
de propiedades
Ejemplo para cambiar literales de una página según el
idioma:
<replace file ="${src}/index.html"
replacefilterfile="messages_${idioma}.properties"
summary="yes">
</replace>
21
replace con replacefilter: reemplaza en un archivo
un token multilínea por un valor multilínea
Ejemplo :
<replace file ="${src}/datos.txt" summary="yes">
<replacefilter
token="multi line${line.separator}line2”
value=
"EL${line.separator}CIELO${line.separator} AZUL" />
</replace>
before
multi line
line2
after
transformación
before
EL
CIELO
AZUL
after
22
Tareas con expresiones regulares
trabajando por línea
replaceregexp: reemplaza la ocurrencia
(concordancia) de una expresión regular por un
patrón de sustitución en un archivo o conjunto de
archivos
Ejemplo 1: en un archivo de propiedades cambiar el nombre
de propiedad OldProperty por NewProperty preservando el
valor que tiene la propiedad :
<replaceregexp file="${src}/build.properties"
match="OldProperty=(.*)" \1 representa cadena concordante
replace="NewProperty=\1"
byline="true"
para el grupo (en este ej. es el valor
de la propiedad
/>
cada línea es una cadena a analizar (byline="true")
23
Ejemplo 2: cambiar el nombre de propiedad OldProperty
por NewProperty preservando el valor que tiene la propiedad
, en un conjunto de archivos de propiedades:
<replaceregexp
match="OldProperty=(.*)"
replace="NewProperty=\1"
byline="true">
<fileset dir=".">
<include name="*.properties"/>
</fileset>
</replaceregexp>
24
Ejemplo 3: anteceder al nombre de toda propiedad la
cadena New preservando el valor que tiene la propiedad , en
un conjunto de archivos de propiedades:
palabra
<replaceregexp
match="([^\s]+)=([^\s]+)"
\1 representa cadena concordante para
el grupo 1 (i.e. nombre de la propiedad
replace="New\1=\2"
\2 representa cadena concordante para
byline="true"
el grupo 2 (i.e. valor de propiedad)
<fileset dir=".">
<include name="*.properties"/>
</fileset>
</replaceregexp>
25
Ejemplo 4: agregar cambio de linea a toda cadena que
antecede un = y que no empieza al principio de la linea
realizar varios reemplazos por línea (flags="g")
<replaceregexp file="${src}/build.properties"
blancos
palabra
palabra
match="([\s]+)([^\s]+)=([^\s]+)"
replace="${line.separator}\2=\3"
byline="true"
flags="g"
/>
transformación
aa=value4
bb=value
yy=value5
zz=56
aa=value4
yy=value5
zz=56
bb=value
26
Ejemplo 5: en un archivo reemplazar varios blancos
(espacios y/o tabs) por un solo blanco:
<replaceregexp
file="${destino}/Archivo-quitar-blancos.html"
match="\s+"
replace=" "
flags="g"
byline="true">
</replaceregexp>
<html>
<h1>
<body>
T E S T
</h1>
</body></html>
transformación
<html> <body>
<h1> T E S T </h1>
</body></html>
hace varios reemplazos por línea (flags="g")
\s no concuerda con cambio de línea
27
Tareas con filtros sobre archivos y
expresiones regulares
filterchain dentro de un copy: permite aplicar
múltiples “filtros” a un archivo de texto (copiado)
para trabajar en modo pipeline
Cada filtro puede hacer un reemplazo de texto en
el archivo, utilizando replaceregex
Ejemplo 1: Agregar un copyright a un archivo java, antes de
la cadena package
// modulo
// modulo
X
package com.acme.proy1;
import java.util.*;
import java.io.*;
. . .
transformación
X
/* copyrigth
mas copyright */
package com.acme.proy1;
import java.util.*;
import java.io.*;
. . .
28
• solución del ejemplo 1:
<copy todir="${destino}" overwrite="true">
<fileset dir="${fuente}">
<include name="**/Miclase1.java"/>
</fileset>
la unidad de token es todo el archivo que es
pasado al comando replaceregex (otros tokenizers
posibles: LineTokenizer, StringTokenizer)
<filterchain>
<tokenfilter>
<filetokenizer />
<replaceregex pattern ="(.*)package"
replace=
"\1${line.separator}
/* copyrigth${line.separator}
mas copyright */
${line.separator}package"
/>
</tokenfilter>
</filterchain>
</copy>
• todo el archivo (token) es la cadena a analizar,pues por defecto
supone byline="false"
• solo hace el primer reemplazo porque no está flags="g"
Ejemplo 2: Agregar en un conjunto de archivos java un
comentario de clase antes de la línea que define la clase
• ilustración:
. . .
public class Aa extends Ii {
. . .
transformación
. . .
/** comentario de clase Aa
*/
public class Aa extends Ii {
. . .
30
• solución del ejemplo 2: ilustra grupos anidados en la regexp
<copy todir="${destino}" overwrite="true">
<fileset dir="${fuente}">
<include name="**/*.java"/>
</fileset>
<filterchain>
<tokenfilter>
grupo2: nombre de clase
<filetokenizer />
<replaceregex
pattern="(public[\s]+class[\s]+([^\s]+))"
grupo1: public class Myclass
replace=
"/** comentario de clase\2
${line.separator}*/
${line.separator}\1"
/>
</tokenfilter>
</filterchain>
</copy>
• todo el archivo (token) es la cadena a analizar,pues por defecto
supone byline="false"
• solo hace el primer reemplazo porque no está flags="g"
• pueden ir varios comandos replaceregex consecutivos
Tareas para extraer una parte de un archivo
y asignarla a una propiedad
loadfile permite asignar a una propiedad el texto
de un archivo que concuerda con una expresión
regular
la propiedad obtenida puede usarse para insertar
su valor en otro archivo
Ejemplo 1: El texto a identificar en un archivo java
corresponde a toda la sección de los imports. Se quiere
asignar a una propiedad myproperty
package com.acme.demo;
import com.acme.demo.*;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.framework.EntityQuery;
@Name("parameterList")
public class ParameterList extends EntityQuery {
. . .
ultimoMetodo() {
}
}
32
NOTA: [^}]*}
concordaría con siguiente }
• solución del ejemplo 1:
<loadfile srcFile="${fuente}/MiClase1.java"
property="myproperty">
<filterchain>
<tokenfilter>
<filetokenizer />
<replaceregex
pattern=
"(.*)package(.*)[${line.separator}][\s*]
[${line.separator}](import.*)@Name(.*)}(.*)}"
grupo 3:sección imports
replace="\3"
flags="s"
ultimos dos }
Singleline: el caracter . concuerda
con cualquier caracter incluso con
cambio de línea
/>
</tokenfilter>
</filterchain>
</loadfile>
<echo message="myproperty es = ${myproperty}" />
Con una propiedad que representa un texto
multilínea, se puede insertar dicho texto en un
archivo destino
package com.acme.demo;
@Name("parameterList")
public class ParameterList extends EntityQuery {
. . .
}
transformación
package com.acme.demo;
myproperty
import com.acme.demo.*;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.framework.EntityQuery;
@Name("parameterList")
public class ParameterList extends EntityQuery {
. . .
}
34
• insertar myproperty en archivo destino
<copy todir="${destino}" overwrite="true">
<fileset dir="${fuente}" casesensitive="yes">
<include name="**/MyClase1.java"/>
</fileset>
<filterchain>
<tokenfilter>
<fileTokenizer />
<replaceregex
pattern=
"(package.*)[${line.separator}]*(@Name)"
replace=
"\1${line.separator}
${myproperty}
${line.separator}\2"
flags="s"
/>
</tokenfilter>
</filterchain>
</copy>
Trabajar con expresiones regulares
desde Java
36
Tarea para extraer una parte de un archivo y
asignarla a una variable
la variable obtenida puede usarse para insertar su
valor en otro archivo
Ejemplo 1: El texto a identificar en un archivo java
corresponde a toda la sección de los imports. Se quiere
asignar a una propiedad seccion
package com.acme.demo;
import com.acme.demo.*;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.framework.EntityQuery;
@Name("parameterList")
public class ParameterList extends EntityQuery {
. . .
ultimoMetodo() {
}
}
37
Solución en Java : extraer sección de imports
import java.util.regex.Matcher;
import java.util.regex.Pattern;
. . .
/* archivos fuente y destino: */
String file1, file2;
String rootPath = "../regular-expressions";
file1 = Util.fileToString(rootPath + "/fuente/Miclase.java");
file2 = Util.fileToString(rootPath + "/fuente/OtraClase.java");
String lineSeparator = System.getProperty("line.separator");
String seccion = null;
/* expresion regular que describe el archivo fuente */
Pattern p = Pattern.compile(
"(.*)package(.*)[" + lineSeparator
+ "][\\s*][" + lineSeparator + "]"
+ "(import.*)@Name(.*)}(.*)}",
Pattern.MULTILINE | Pattern.DOTALL);
Matcher m = p.matcher (file1);
/* se asigna a variable seccion el grupo 3 de concordancia */
if (m.find()) {
seccion = m.group(3);
}
38
asignar sección de imports a archivo destino
/* expresion regular describe lugar para insertar imports
en archivo destino */
p = Pattern.compile(
"(package.*)[" + lineSeparator + "]*(@Name)",
Pattern.MULTILINE | Pattern.DOTALL);
m = p.matcher (file2);
/* modifica el archivo destino insertando los imports */
file2 = m.replaceAll(
"$1" + lineSeparator + seccion + lineSeparator + "$2");
39
target en ANT que invoca programa Java
<target name="imports">
<echo message="JAVA: toma imports de un archivo y
los coloca en segundo archivo" />
<java classpath="${classes.dir}"
classname=
"co.edu.javeriana.generadores.regexp.TomarImports">
</java>
</target>
Descargar