Skip to content

Latest commit

 

History

History
240 lines (205 loc) · 10.7 KB

File metadata and controls

240 lines (205 loc) · 10.7 KB

7.3 Expresiones regulares

Las expresiones regulares ("Regexp") son una herramienta compliacada pero poderosa para la coincidencia de patrones y manipulación del texto. Sin embargo no se desempeñan tan bien como las coincidencias de texto puro, son mas flexibles. Basadas en su sintaxis, puedes filtrar casi cualquier tipo de texto de tu fuente d econtenidos. Si quieres recolectar datos en desarrollo web, no es dificil utilizar las Expresiones regulares para obtener datos importantes.

Go tiene un paquete regexp, que provee el soporte oficial para expresiones regulares. Si ya has usado expresiones regulares en otros lenguajes de programación, puedes estar familiarizado con ellas. Nota que Go implemente el estándar RE2, excepto por \C. Para mas detalles sigue el siguiente enlace: http://code.google.com/p/re2/wiki/Syntax.

El paquete strings de Go puede hacer muchos trabajos como búsqueda (Contains, Index), reemplace, (Replace), análisis (Split, Join), etc. y es mucho mas rápido que las expresiones regulares. Sin embargo, todas esas son operaciones triviales. Si quieres buscar una cadena sin tener en cuenta mayúsculas o minúsculas, las expresiones regualres serán tu mejor opción. Entonces, si el paquete strings es suficiente para tus necesidades, úsalo porque es mas fácil de leer y entender; si necesitas hacer operaciones mas avanzadas, usa las expresiones regulares.

Si recuerdas la sección anterior de validación de formularios, usamos expresiones regulares para validar la información de entrada del usuario. Se conciente que todos los caracteres están en UTF-8. ¡Vamos a aprender mas del paquete regexp!.

Coincidencias

El paquete regexp tiene tres funciones para coincidencias: Si encuentra una coincidencia retorna true, de lo contrario false.

	func Match(pattern string, b []byte) (matched bool, error error)
	func MatchReader(pattern string, r io.RuneReader) (matched bool, error error)
	func MatchString(pattern string, s string) (matched bool, error error)

Las tres funciones verifican si pattern tiene una coincidencia en la entrada. retornando true si hay coincidencia. Sin embargo, si la expresión regular tiene errores, retornará un error. Los tres tipos de entradas aquí son []byte, RuneReader y string.

Aquí está un ejemplo de como verificar una dirección IP:

	func IsIP(ip string) (b bool) {
		if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m {
			return false
		}
		return true
	}

Como puedes ver, usando un patrón en el paquete regexp no es tan diferente. Aquí hay otro ejemplo de verificar si la entrada de un usuario es válida:

	func main() {
		if len(os.Args) == 1 {
			fmt.Println("Usage: regexp [string]")
			os.Exit(1)
		} else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m {
			fmt.Println("Number")
		} else {
			fmt.Println("Not number")
		}
	}

En el ejemplo anterior usamos Match(Reader|String) para verificar si el contenido es álido, y ambos son fáciles de usar.

Filtros

El modo Match puede verificar el contenido pero no cortarlo, filtrarlo o recoger información de él. Si quieres hacer esto, tienes que usar el modo complejo de las regexp.

Digamos que necesitamos escribir un crawler. Aquí un ejemplo de como usar las expresiones regulares para filtrar y cortar los datos.

	package main

	import (
		"fmt"
		"io/ioutil"
		"net/http"
		"regexp"
		"strings"
	)

	func main() {
		resp, err := http.Get("http://www.baidu.com")
		if err != nil {
			fmt.Println("http get error.")
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Println("http read error")
			return
		}

		src := string(body)

		// Convertimos las etiquetas HTML a minúsculas.
		re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
		src = re.ReplaceAllStringFunc(src, strings.ToLower)

		// Removemos los estilos..
		re, _ = regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")
		src = re.ReplaceAllString(src, "")

		// Removemos los scripts.
		re, _ = regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")
		src = re.ReplaceAllString(src, "")

		// Removemos todas las etiquetas html y las reemplazamos con saltos de línea.
		re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
		src = re.ReplaceAllString(src, "\n")

		// Removemos los saltos de línea contínuos.
		re, _ = regexp.Compile("\\s{2,}")
		src = re.ReplaceAllString(src, "\n")

		fmt.Println(strings.TrimSpace(src))
	}

En este ejemplo, usamos Compile como un primer paso para el modo complejo. Esto verifica que la sintaxis de la expresión regular esté correcta, entonces retorna la regexp para analizar el contenido en otras operaciones.

Aquí están algunas funciones para analizar la sintaxis de las expresiones regulares:

	func Compile(expr string) (*Regexp, error)
	func CompilePOSIX(expr string) (*Regexp, error)
	func MustCompile(str string) *Regexp
	func MustCompilePOSIX(str string) *Regexp

La diferencua entre ComplePOSIX y Compile es que el formador no tiene que usar la sintaxis POSIX cuando es una búsqueda de mas a la izquierda, y en el otro, únicamente la búsqueda de mas a la izquierda. Por ejemplo, para la expresión [a-z]{2,4} y el contenido "aa09aaa88aaaa", CompilePOSIX retornará aaa pero Compile retornará aa. El prefijo Must significa un panic cuando la expresión regular no es correcta, retornando un error de otra manera.

Ahora que sabemos como crear expresiones regulares, vamos a ver cómo los métodos nos proveen una manera para operar con el contenido:

	func (re *Regexp) Find(b []byte) []byte
	func (re *Regexp) FindAll(b []byte, n int) [][]byte
	func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
	func (re *Regexp) FindAllString(s string, n int) []string
	func (re *Regexp) FindAllStringIndex(s string, n int) [][]int
	func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string
	func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int
	func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
	func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
	func (re *Regexp) FindIndex(b []byte) (loc []int)
	func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int)
	func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int
	func (re *Regexp) FindString(s string) string
	func (re *Regexp) FindStringIndex(s string) (loc []int)
	func (re *Regexp) FindStringSubmatch(s string) []string
	func (re *Regexp) FindStringSubmatchIndex(s string) []int
	func (re *Regexp) FindSubmatch(b []byte) [][]byte
	func (re *Regexp) FindSubmatchIndex(b []byte) []int

Estos 18 métodos contienen funciones idénticas para distintos tipos de entredad (segmentos, cadenas e io.RuneReader), entonces vamos a simplificar la lista ignorando algunos tipos de entrada como sigue:

	func (re *Regexp) Find(b []byte) []byte
	func (re *Regexp) FindAll(b []byte, n int) [][]byte
	func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
	func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
	func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
	func (re *Regexp) FindIndex(b []byte) (loc []int)
	func (re *Regexp) FindSubmatch(b []byte) [][]byte
	func (re *Regexp) FindSubmatchIndex(b []byte) []int

Código de ejemplo:

	package main

	import (
		"fmt"
		"regexp"
	)

	func main() {
		a := "I am learning Go language"

		re, _ := regexp.Compile("[a-z]{2,4}")

		// Encontrar la primera coincidencia.
		one := re.Find([]byte(a))
		fmt.Println("Find:", string(one))
		// Encontrar todas las coincidencias y guardarlas en un segmento, n menor a 0 significa todas las coincidencias, indicar el tamaño del segmento si es mayor a 0.
		all := re.FindAll([]byte(a), -1)
		fmt.Println("FindAll", all)

		// Encuentra el índice de la primera coincidencia, posición de inicio y fin.
		index := re.FindIndex([]byte(a))
		fmt.Println("FindIndex", index)

		// Encontrar los índices de todas las coincidencias, n hace lo mismo que el de arriba.
		allindex := re.FindAllIndex([]byte(a), -1)
		fmt.Println("FindAllIndex", allindex)

		re2, _ := regexp.Compile("am(.*)lang(.*)")

		// Encuentra la primera subcoincidencia y retorna un arreglo, el primero elemento contiene todos los elementos, el segundo contiene el resultado del primer (), en tercero, los resultados del segundo ()
		// Salida:
		// Primer elemento: "am learning Go language"
		// Segundo elemento: " learning Go ", Note que los espacios están en la salida.
		// Tercer elemento: "uage"
		submatch := re2.FindSubmatch([]byte(a))
		fmt.Println("FindSubmatch", submatch)
		for _, v := range submatch {
			fmt.Println(string(v))
		}

		// Igual que FindIndex().
		submatchindex := re2.FindSubmatchIndex([]byte(a))
		fmt.Println(submatchindex)

		// FindAllSubmatch, Encontrar todas las subcoincidencias.
		submatchall := re2.FindAllSubmatch([]byte(a), -1)
		fmt.Println(submatchall)

		// FindAllSubmatchIndex, Encontrar todas las subcoincidencias.
		submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1)
		fmt.Println(submatchallindex)
	}

Como mencionamos anteriormente, Las expresiones regulares tienen 3 métodos para coincidencias, ellos hace exactamente lo mismo que las funciones exportadas. De hecho, estas funciones pueden ser exportadas:

	func (re *Regexp) Match(b []byte) bool
	func (re *Regexp) MatchReader(r io.RuneReader) bool
	func (re *Regexp) MatchString(s string) bool

Ahora veamos como reemplazar cadenas usando expresiones regulares:

	func (re *Regexp) ReplaceAll(src, repl []byte) []byte
	func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte
	func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte
	func (re *Regexp) ReplaceAllLiteralString(src, repl string) string
	func (re *Regexp) ReplaceAllString(src, repl string) string
	func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string

Estos son usadas en el ejemplo del crawler, entonces no se explicarán mas aquí.

Veamos la definición de Expand:

	func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte
	func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte

¿Cómo usamos Expand?

	func main() {
		src := []byte(`
			call hello alice
			hello bob
			call hello eve
		`)
		pat := regexp.MustCompile(`(?m)(call)\s+(?P<cmd>\w+)\s+(?P<arg>.+)\s*$`)
		res := []byte{}
		for _, s := range pat.FindAllSubmatchIndex(src, -1) {
			res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s)
		}
		fmt.Println(string(res))
	}

A este punto, hemos aprendido el paquete entero de regexp en Go, espero que ahora puedas entender más usando los métodos claves, entonces podrás hacer algo interesante por ti mismo.

Enlaces