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>