sábado, 24 de diciembre de 2016

Expresiones Regulares en Java con saltos de linea y Carácter predefinido: Dot o Punto

Hace unos días en el trabajo me vi en la necesitad de aplicar expresiones regulares, pero que estas involucraban a más de una linea. Supongamos que tenemos un texto en donde registramos nuestras compras, tenemos permitido comprar bebidas y papas fritas, siempre y cuando sean por separados, ya que consumir papas fritas y bebidas en una misma compra, ello afecta seriamente nuestra salud, pero podemos hacer una excepción y consumir sólo bebida o sólo papas fritas, para hacerlo más fácil, diremos que las bebidas y papas fritas aparecen juntas
El registro lo debemos entregar a un supervisor así que antes de entregarlo debemos maquillarlo, no queremos que nos llamen la atención.

Tenemos el siguiente registro(Registro 1)

Venta: 1 Fecha: 12/12/2016
Arroz  1250
Atun 3540
Bebida 2300
Fin Venta

Venta: 2 Fecha: 13/12/2016
Nueces  1250
Pasas 3540
Papas Fritas 2300
Fin Venta

Venta: 3 Fecha: 14/12/2016
Huevos  1250
Leche 3540
Bebida 2300
Papas Fritas 3200
Fin Venta

Venta: 4 Fecha: 15/12/2016
Papel Higienico  1250
Carne Molida 3540
Bebida 1590
Fin Venta



Para las ventas: 1, 2 y 4, no tenemos ningún problema, ya que nunca se compro bebidas y papas fritas. Sin embargo, en la venta 3 si compramos Bebidas y Papas Fritas, debemos borrar la evidencia.

El ejemplo tiene pocas ventas y podemos modificar la venta manualmente, pero ¿que pasa cuando tengamos cientos de ventas? Bueno, pues para ello haremos un pequeño programa, necesitamos encontrar patrones de Bebidas y Papas Fritas y borrarlos del registro, el precio es variable, mi primer acercamiento fue usar la siguiente expresión regular:
"\s*Bebida\s+\d+\s*\n\s*Papas Fritas\s+\d+\s*"
Donde:
\s*: Nos dice que puede haber 0 o más espacios vacíos
\s+: Nos dice que debe haber al menos un espacio vacío o muchos.
\d+: Nos dice que debe haber al menos un digito
\n: Un salto de linea

Pues bien, al probar esta expresión regular, usando la opción find() del matcher, pues funcionaba de maravilla, pero que pasaría si ademas de ventas, en nuestros registros hay movimientos, en donde los movimientos están permitidos bebidas y papas
(Registro 2)
Movimiento 1: Fecha: 14/12/2016
Huevos  1250
Leche 3540
Bebida 2300
Papas Fritas 3200
Fin Movimiento



Bien, Usemos nuestra expresión regular para los dos registros
Código fuente del programa


import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author Cliente
 */
public class TestRegex {
 public static void main(String args[]){
  String text1 = "Venta: 1 Fecha: 12/12/2016\n"
      + "Arroz  1250\n"
      + "Atun 3540\n"
      + "Bebida 2300\n"
      + "Fin Venta\n"
      + "Venta: 2 Fecha: 13/12/2016\n"
      + "Nueces  1250\n"
      + "Pasas 3540\n"
      + "Papas Fritas 2300\n"
      + "Fin Venta\n"
      + "Venta: 3 Fecha: 14/12/2016\n"
      + "Huevos  1250\n"
      + "Leche 3540\n"
      + "Bebida 2300\n"
      + "Papas Fritas 3200\n"
      + "Fin Venta\n"
      + "Venta: 4 Fecha: 15/12/2016\n"
      + "Papel Higienico  1250\n"
      + "Carne Molida 3540\n"
      + "Bebida 1590\n"
      + "Fin Venta\n";
  
  String text2 = "Movimiento 1: Fecha: 14/12/2016\n"
      + "Huevos  1250\n"
      + "Leche 3540\n"
      + "Bebida 2300\n"
      + "Papas Fritas 3200\n"
      + "Fin Movimiento";
  
  String regex = "\\s*Bebida\\s+\\d+\\s*\\n\\s*Papas Fritas\\s+\\d+\\s*";
  
  
  Pattern pattern = Pattern.compile(regex);
  
  System.out.println("*******Test Registro 1******");
  System.out.println("Before:\n" + text1);
  System.out.println("find()" + pattern.matcher(text1).find());
  System.out.println("matches()" + pattern.matcher(text1).matches());
  System.out.println("After\n" + (pattern.matcher(text1).replaceAll("\n")));
  
  System.out.println("*******Test Registro 2******");
  System.out.println("Before:\n" + text2);
  System.out.println("find()" + pattern.matcher(text2).find());
  System.out.println("matches()" + pattern.matcher(text2).matches());
  System.out.println("After\n" + (pattern.matcher(text2).replaceAll("\n")));
 }
}

Aquí la salida al ejecutar el programa

Before:
Venta: 1 Fecha: 12/12/2016
Arroz  1250
Atun 3540
Bebida 2300
Fin Venta
Venta: 2 Fecha: 13/12/2016
Nueces  1250
Pasas 3540
Papas Fritas 2300
Fin Venta
Venta: 3 Fecha: 14/12/2016
Huevos  1250
Leche 3540
Bebida 2300
Papas Fritas 3200
Fin Venta
Venta: 4 Fecha: 15/12/2016
Papel Higienico  1250
Carne Molida 3540
Bebida 1590
Fin Venta

find()true
matches()false
After
Venta: 1 Fecha: 12/12/2016
Arroz  1250
Atun 3540
Bebida 2300
Fin Venta
Venta: 2 Fecha: 13/12/2016
Nueces  1250
Pasas 3540
Papas Fritas 2300
Fin Venta
Venta: 3 Fecha: 14/12/2016
Huevos  1250
Leche 3540
Fin Venta
Venta: 4 Fecha: 15/12/2016
Papel Higienico  1250
Carne Molida 3540
Bebida 1590
Fin Venta

*******Test Registro 2******
Before:
Movimiento 1: Fecha: 14/12/2016
Huevos  1250
Leche 3540
Bebida 2300
Papas Fritas 3200
Fin Movimiento
find()true
matches()false
After
Movimiento 1: Fecha: 14/12/2016
Huevos  1250
Leche 3540
Fin Movimiento

Como ven, también nuestro registro de Movimiento fue afectado, entonces, la solución definitiva es usar matches() y no find. Así que modificamos nuestra expresión regular para poder hacer matches() a todo el texto, aquí la nueva expresión regular

"(?s)((Venta.+\\s*Bebida\\s+\\d+\\s*\\n\\s*Papas Fritas\\s+\\d+\\s*.+Fin Venta\n)(\n)*)+"

Donde:
.+: Indica que entre el texto Venta y Bebida, puede tener más texto, cual quier texto, no importa, esto es necesario ya que las lineas son variables, entre Venta y Bebida puedo tener más lineas, incluyendo el carácter especial (\n) o Line Feed
(?s): Este flag conocido como DOTALL, habilita el modo DOTALL el cual permite considerar el carácter especial (\n) cómo cualquier carácter, sin este flag, nuestro expresión regular nunca hará matches()

Aquí en nuevo codigo fuente y la salida correspondiente




       
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.rhiscom.virtualpos;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author Cliente
 */
public class TestRegex {
 public static void main(String args[]){
  String text1 = "Venta: 1 Fecha: 12/12/2016\n"
      + "Arroz  1250\n"
      + "Atun 3540\n"
      + "Bebida 2300\n"
      + "Fin Venta\n"
      + "Venta: 2 Fecha: 13/12/2016\n"
      + "Nueces  1250\n"
      + "Pasas 3540\n"
      + "Papas Fritas 2300\n"
      + "Fin Venta\n"
      + "Venta: 3 Fecha: 14/12/2016\n"
      + "Huevos  1250\n"
      + "Leche 3540\n"
      + "Bebida 2300\n"
      + "Papas Fritas 3200\n"
      + "Fin Venta\n"
      + "Venta: 4 Fecha: 15/12/2016\n"
      + "Papel Higienico  1250\n"
      + "Carne Molida 3540\n"
      + "Bebida 1590\n"
      + "Fin Venta\n";
  
  String text2 = "Movimiento 1: Fecha: 14/12/2016\n"
      + "Huevos  1250\n"
      + "Leche 3540\n"
      + "Bebida 2300\n"
      + "Papas Fritas 3200\n"
      + "Fin Movimiento";
  
  String regex = "(?s)((Venta.+\\s*Bebida\\s+\\d+\\s*\\n\\s*Papas Fritas\\s+\\d+\\s*.+Fin Venta\n)(\n)*)+";
  String regexToReplace = "\\s*Bebida\\s+\\d+\\s*\\n\\s*Papas Fritas\\s+\\d+\\s*";
  
  Pattern pattern = Pattern.compile(regex);
  
  System.out.println("*******Test Registro 1******");
  System.out.println("Before:\n" + text1);
  System.out.println("matches()" + pattern.matcher(text1).matches());
  if (pattern.matcher(text1).matches()) {
   System.out.println("After\n" + (text1.replaceAll(regexToReplace, "\n")));
  }

  System.out.println("*******Test Registro 2******");
  System.out.println("Before:\n" + text2);
  System.out.println("matches()" + pattern.matcher(text2).matches());
  if (pattern.matcher(text2).matches()) {
   System.out.println("After\n" + (text2.replaceAll(regexToReplace, "\n")));
  }
 }
}

Salida:
*******Test Registro 1******
Before:
Venta: 1 Fecha: 12/12/2016
Arroz  1250
Atun 3540
Bebida 2300
Fin Venta
Venta: 2 Fecha: 13/12/2016
Nueces  1250
Pasas 3540
Papas Fritas 2300
Fin Venta
Venta: 3 Fecha: 14/12/2016
Huevos  1250
Leche 3540
Bebida 2300
Papas Fritas 3200
Fin Venta
Venta: 4 Fecha: 15/12/2016
Papel Higienico  1250
Carne Molida 3540
Bebida 1590
Fin Venta

matches()true
After
Venta: 1 Fecha: 12/12/2016
Arroz  1250
Atun 3540
Bebida 2300
Fin Venta
Venta: 2 Fecha: 13/12/2016
Nueces  1250
Pasas 3540
Papas Fritas 2300
Fin Venta
Venta: 3 Fecha: 14/12/2016
Huevos  1250
Leche 3540
Fin Venta
Venta: 4 Fecha: 15/12/2016
Papel Higienico  1250
Carne Molida 3540
Bebida 1590
Fin Venta

*******Test Registro 2******
Before:
Movimiento 1: Fecha: 14/12/2016
Huevos  1250
Leche 3540
Bebida 2300
Papas Fritas 3200
Fin Movimiento
matches()false

       
 

Por último, hagan una prueba sin el flag "(?s)" y veran que la expresión regular nunca hará matches, otra forma de habilitar el modo DOTALL, es al crear la instancia Pattern:

Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);




No hay comentarios:

Publicar un comentario

Hola A todos