XML es usado comúnmente como formato de comunicación de datos en servicios web. Hoy, está asumiento un rol mas y mas importante en el desarrollo web. En esta sección, vamos a introducir el cómo trabajar con XML a través de la librería estándar de Go.
No voy a hacer ningún intento de enseñar la sintaxis o convenciones. Para esto, por favor lea la documentación sobre XML. Nosotros nos enfocaremos en como codificar y decodificar archivos XML en Go.
Supón que trabajas con información y tecnología y tienes que lidiar con el siguiente archivo de configuración en XML:
<?xml version="1.0" encoding="utf-8"?>
<servers version="1">
<server>
<serverName>Shanghai_VPN</serverName>
<serverIP>127.0.0.1</serverIP>
</server>
<server>
<serverName>Beijing_VPN</serverName>
<serverIP>127.0.0.2</serverIP>
</server>
</servers>
El ejemplo superior contiene dos tipos de información sobre el servidor: El nombe del servidor y la IP. vamos a usar este documento en los siguientes ejemplos.
¿Cómo analizamos este documento XML? Podemos usar la función Unmarshal
del paquete xml
de Go para hace esto.
func Unmarshal(data []byte, v interface{}) error
El parámetro data
recibe un flujo de una fuente XML, y v
es la estructura a la que quieres que vaya la salida analizada del XML. Esta es una interface, lo que significa que puedes convertir un XML a cualquier estructura que tu desees.Aquí, vamos a hablar de como convertir el XML al tipo struct
desde que tenga estructuras similares.
Código de ejemplo:
package main
import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"
)
type Recurlyservers struct {
XMLName xml.Name `xml:"servers"`
Version string `xml:"version,attr"`
Svs []server `xml:"server"`
Description string `xml:",innerxml"`
}
type server struct {
XMLName xml.Name `xml:"server"`
ServerName string `xml:"serverName"`
ServerIP string `xml:"serverIP"`
}
func main() {
file, err := os.Open("servers.xml") // Para acceso de lectura
if err != nil {
fmt.Printf("error: %v", err)
return
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
fmt.Printf("error: %v", err)
return
}
v := Recurlyservers{}
err = xml.Unmarshal(data, &v)
if err != nil {
fmt.Printf("error: %v", err)
return
}
fmt.Println(v)
}
XML es actualmente una estructura de datos tipo arbol, y podemos definir una estructura muy similar en Go, luego usamos xml.Unmarshal
para convertir el XML a la estructura del objeto. El ejemplo de código imprimirá el siguiente contenido:
{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}]
<server>
<serverName>Shanghai_VPN</serverName>
<serverIP>127.0.0.1</serverIP>
</server>
<server>
<serverName>Beijing_VPN</serverName>
<serverIP>127.0.0.2</serverIP>
</server>
}
Usamos xml.Unmarshal
para analizar el documento XML a la correspondiente estructura de objetos. También puedes ver que tenemo algo como xml:serverName
en nuestra estructura. Esta es una característica de las estructuras llamadas struct tags
para ayudar con la reflexión. Veamos la definición de Unmarshal
de nuevo:
func Unmarshal(data []byte, v interface{}) error
El primer argumento es un flujo de datos XML. El segundo argumento es el tipo de almacenamiento y soporta estructuras, segmentos y cadenas. El paquete XML de Go usa reflexión para el mapeo de datos, entonces todos los campos de v deberían ser exportados. Sin embargo, esto causa un problema: ¿Cómo sabemos que campo XML corresponde a la campo de la estructura mapeada? La respuesta está en el analizador de XML que aaliza la información en cierto orden. La librería permite encontrar la mejor coincidencia en la etiqueta de la estructura primero. Si una coincidencia no puede ser encontrada, entonces busca por los nombre de los campos. Se conciente que todas las etiquetas, nombres de campos y elementos del XML son sensibles a mayúsculas y minúsculas, entonces debes asegurarte que hay una correspondencia uno a uno para que el mapeo sea exitoso.
El mecanismo de reflexión de Go permite usar la información de la etiqueta para reflectar información del XML a la estructura del objeto. Si quieres saber mas sobre reflexión en Go, por favor dirígete a la documentación del paquete de reflexión y etiqueas en Go.
Aquí hay algunas reglas para el uso del paquete xml
para la transformación de documentos XMl a estructuras:
- Si el campo de la es una cadena o un []byte con la etiqueta
",innerxml"
,Unmarshal
asignará la información XML pura a el, como la descripción del ejemplo superior:
Shanghai_VPN127.0.0.1Beijing_VPN127.0.0.2
- Si el campo es llamado
XMLName
y su tipo esxml.Name
entonces obtendrá el nombre del elemento, comoservers
en el ejemplo superior. - Si la etiqueta de un campo contiene el nombre de un elemento, entonces obtendrá el nombre del elemento como
servername
yserverip
en el ejemplo superior. - Si el la etiqueta del campo contiene
",attr"
, entonces el obtendrá el atributo del elemento, comoversion
en el ejemplo superior. - Si una etiqueta de campo contiene algo como
"a>b>c"
, esta obtendrá el valor del elemento c del nodo b del nodo a - Si un campo contiene
"="
, entonces obtendrá nada. - Si un campo contiene
",any"
entonces obtendrá todos los elementos que no encaje con las otras reglas. - Si el elemento XML tiene uno o mas comentarios, todos esos comentarios serán agregados al primer campo que contenga
",comments"
. Este tipo de campo puede ser una cadeana o un []byte. Si este no existe, todos los comentarios serán eliminados.
Estas reglas te dirán como definir etiqueta en estructuras. Una vez entiendas estas reglas, el mapeo de XML a estructuras será tan fácil como el ejemplo de arriba. Porque las etiquetas XML y los elementos XML tieen una correspondencia uno a uno. también podemos usar segmentos para representar múltiples elementos en el mismo nivel.
Nota que todos los campos de la estructura deben ser exportados (Primera en mayúscula) para que se puedan analizar correctamente.
Si lo que queremos es producir un documento XML en vez de analizar uno. ¿Cómo hacemos esto en Go? Sorprendentemente, el paquete xml
provee dos funciones que son Marshal
y MarshalIndent
, donde la segunda opción automáticamente identa el documento XML 'marshalizado'. Las definiciones son las siguientes:
func Marshal(v interface{}) ([]byte, error)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
El primer argumento de ambas de estas funciones es para almacenar el flujo de datos XML.
Veamos un ejemplo de como esto funciona:
package main
import (
"encoding/xml"
"fmt"
"os"
)
type Servers struct {
XMLName xml.Name `xml:"servers"`
Version string `xml:"version,attr"`
Svs []server `xml:"server"`
}
type server struct {
ServerName string `xml:"serverName"`
ServerIP string `xml:"serverIP"`
}
func main() {
v := &Servers{Version: "1"}
v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"})
v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"})
output, err := xml.MarshalIndent(v, " ", " ")
if err != nil {
fmt.Printf("error: %v\n", err)
}
os.Stdout.Write([]byte(xml.Header))
os.Stdout.Write(output)
}
El ejemplo superior imprime la siguiente información:
<?xml version="1.0" encoding="UTF-8"?>
<servers version="1">
<server>
<serverName>Shanghai_VPN</serverName>
<serverIP>127.0.0.1</serverIP>
</server>
<server>
<serverName>Beijing_VPN</serverName>
<serverIP>127.0.0.2</serverIP>
</server>
</servers>
Como hemos definido en el ejemplo anterior, la razón por que que tenemos un os.Stdout.Write([]byte(xml.Header))
es porque ambos xml.MarshalIndent
y xml.Marshal
no generan los encabezados XML por ellos mismos, entonces tenemos que específicamente imprimirlos para tener los documentos XML producidos correctamente.
Aquí vemos que Marshal
también recibe un parámetro de tipo interface{}
. Entonces ¿Cuáles son las reglas cuando 'marshaleamos' un documento XML?
- Si v es un arreglo o segmento, imprimirá los valores como un valor.
- Si v es un puntero, imprimirá el contenido al que v está apuntando, imprimiendo nada cuando v es nil.
- Si v es una interface, manejará la interface también.
- Si v es de cualquier otro tipo, imprimirá el valor de ese tipo.
¿Cómo xml.Marshal
decide el nombre de los elementos? Sigue las siguientes reglas:
- Si v es una estructura, define el en la etiqueta de XMLName.
- Si el campo nombre es XMLName, y el tipo es xml.Name
- Campo etiqueta en la estructura.
- Campo nombre de la estructura
- Nombre de tipo marshal.
Entonces necesitamos definir como asignar las etiquetas en orden de producil el documento XML final.
- XMLName no será impreso.
- Campos que tengan etiquetas que contengan
"-"
no serán impresos. - Si una etiqueta contiene
"name,attr"
se usará como el nombre del atributo y el valor del campo como valor, como versión en el ejemplo superior. - Si la etiqueta contiene
",attr"
, usará el nombre del campo nombre como nombre y el valor del campo como atributo. - Si una etiqueta contiene
",chardata"
, imprimirá el caracter en vez del elemento. - Si una etiqueta contiene
",innerxml"
, imprimirá el valor plano. - Si una etiqueta contiene
",comment"
, la imprimirá como comentario sin escapar, entonces no debes tener "--" en su valor. - Si una etiqueta contiene
"omitempty"
, omitirá este campo si tiene valor cero, incluyendo false, 0, puntero nulo o interfaz nula, arreglos, segmentos, mapas y cadenas de longitud 0 . - Si una etiqueta contiene
"a>b>c"
imprimirá los tres elementos donde a contiene a b y b contienen a c, como el siguiente código.
FirstName string `xml:"name>first"`
LastName string `xml:"name>last"`
<name>
<first>Asta</first>
<last>Xie</last>
</name>
Puedes notar que las etiquetas de estructuras son muy útiles para lidiar con XML, y lo mismo va para otros tipos de formatos de datos, que discutiremos en las siguientes secciones. Si todavía encuentras que tienes problemas con las etiquetas de las estructuras, deberías posiblemente leer sobre la documentación de ellas antes de ver la siguiente sección.
- Índice
- Sección anterior: Archivos de texto
- Siguiente sección: JSON