Manipulando Arquivos CSV e XML em Go
Na lição anterior você aprendeu a utilizar a biblioteca encoding/json
para realizar a manipulação de arquivos JSON
em uma aplicação feita com GoLang.
Mas você sabia que a biblioteca encoding
vai muito além da manipulação desses arquivos? 🧐
Apesar dessa biblioteca ser muito usada para fazer o tratamento de JSON
, ela também é capaz de manipular arquivos CSV
e XML
, por meio de dois pacotes:
encoding/csv
encoding/xml
Na lição de hoje, você irá aprender a utilizar esses dois pacotes para manipular arquivos csv
e xml
🫡
O que são arquivos CSV?
Os arquivos CSV (Comma-Separated Values) são arquivos de texto simples que armazenam dados em formato tabular (como uma planilha), onde os valores são separados por vírgulas (,
), ponto e vírgula (;
) ou outro delimitador, como a própria tabulação (\t
).
Eles são amplamente usados para importar e exportar dados entre diferentes sistemas, como bancos de dados, planilhas (Excel, Google Sheets e etc.) e programas de análise de dados.
Vejamos um exemplo do conteúdo existente em um arquivo .csv
:
Nome,Idade,Profissão
Alice,30,Engenheira
Bob,25,Desenvolvedor
Carlos,40,Designer
Um arquivo CSV básico é formado pela seguinte estrutura:
- Cabeçalho (opcional): onde a primeira linha pode conter os nomes das colunas. (Nome, Idade, Profissão, etc.)
- Dados: onde cada linha representa um registro, com valores separados por um delimitador. (Alice,30,Engenheira, etc.)
Entretanto, existem algumas regras importantes que você precisa saber antes de criar tais arquivos:
- 1) Cada linha representa um novo registro,
- 2) Os valores de um registro são separados por um delimitador (geralmente
,
), - 3) Se um campo contém o próprio delimitador, ele pode ser colocado entre aspas ("exemplo, teste").
- 4) Alguns arquivos
CSV
usam;
ou\t
(tab) como delimitador em vez de,
.
Para que serve um arquivo CSV?
Um arquivo CSV pode ser usado para muitas coisas, tais como:
✔ Importação e Exportação de Dados: ele é um formato comum para transferir dados entre sistemas.
✔ Compatibilidade Universal: pode ser aberto por Excel, Google Sheets, Python, Go, bancos de dados e muitas outras ferramentas que possuem suporte a ele.
✔ Formato Simples e Leve: por ser um arquivo de texto simples, ele tende a ocupar pouco espaço e pode ser lido facilmente.
Vejamos alguns exemplos de dados que podem ser armazenados em um arquivo CSV
:
- Contatos de um aplicativo de e-mail.
- Produtos para uma loja virtual.
- Relatórios financeiros entre sistemas.
Dito isso, vamos aprender a importar essa biblioteca em seu projeto Go 😉
Importando a biblioteca encoding/csv
Por padrão, o GoLang já conta com uma biblioteca que é capaz de fazer a interpretação de um CSV.
Essa biblioteca é chamada de encoding/csv
, e pode ser incluída facilmente dentro do seu projeto da seguinte forma:
import "encoding/csv"
Ou, caso você já tenha outras bibliotecas importadas dentro do seu projeto, basta usar assim:
import (
"..."
"encoding/csv"
)
Quando trabalhamos com o pacote encoding/csv
em Go, os dados do CSV podem ser representados de diferentes formas, dependendo do que queremos fazer, como:
slices
structs
maps
Criando e escrevendo um arquivo CSV
O processo de criação e escrita de um arquivo CSV em GoLang, é um processo muito simples, observe:
package main
import (
"encoding/csv"
"fmt"
"os"
)
func main() {
file, err := os.Create("dados.csv")
if err != nil {
fmt.Println("Erro ao criar arquivo:", err)
return
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// Escrever cabeçalho
writer.Write([]string{"Nome", "Idade", "Profissão"})
// Escrever dados
writer.Write([]string{"Alice", "30", "Engenheira"})
writer.Write([]string{"Bob", "25", "Desenvolvedor"})
fmt.Println("Arquivo CSV criado com sucesso!")
}
No código acima, fizemos o uso de duas bibliotecas principais, a primeira responsável pela leitura do CSV
(encoding/csv
), e a terceira responsável pela manipulação de arquivos (os
).
Note que eu criei um CSV por meio de um slice
(array
) de forma bem simples, antes de salvar o arquivo com modificações:
writer.Write([]string{"Alice", "30", "Engenheira"})
No final, será criado um arquivo chamado dados.csv
com a seguinte estrutura:
Nome,Idade,Profissão
Alice,30,Engenheira
Bob,25,Desenvolvedor
Lendo um arquivo CSV
Para ler um arquivo CSV
existente na sua máquina local, ainda vamos precisar usar as duas bibliotecas, a os
e a encoding/csv
:
package main
import (
"encoding/csv"
"fmt"
"os"
)
func main() {
file, err := os.Open("dados.csv")
if err != nil {
fmt.Println("Erro ao abrir arquivo:", err)
return
}
defer file.Close()
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
fmt.Println("Erro ao ler CSV:", err)
return
}
for _, record := range records {
fmt.Println("Linha:", record)
}
}
O comando acima, faz o uso da função csv.NewReader()
para fazer a interpretação do conteúdo que existe dentro do CSV (dados.csv
), convertendo para um array
, e mostrando seus dados dentro de uma estrutura for
no terminal.
Atualizando um arquivo CSV (sobrescrever)
Supondo que você queria sobrescrever uma determinada linha de um arquivo CSV, como você pode fazer isso?
Simples, basta abrir o CSV normalmente, converter para um array
, modificar a linha desejada, e salvar de volta no mesmo arquivo 😋
Supondo que temos um arquivo chamado dados.csv
com a seguinte estrutura:
ID,Nome,Idade,Profissao
1,Alice,30,Engenheira
2,Bob,25,Desenvolvedor
3,Carlos,40,Designer
E que nós queremos modificar a idade do Bob de 25 para 26, para obter este resultados podemos fazer o uso seguinte lógica:
package main
import (
"encoding/csv"
"fmt"
"os"
"strconv"
)
func main() {
arquivo := "dados.csv"
// 1️⃣ Abrir o arquivo para leitura
file, err := os.Open(arquivo)
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
reader := csv.NewReader(file)
dados, err := reader.ReadAll() // Lê todo o conteúdo do CSV
if err != nil {
fmt.Println("Erro ao ler CSV:", err)
return
}
// 2️⃣ Modificar a linha desejada
for i, linha := range dados {
if linha[0] == "2" { // Procuramos a linha onde o ID é "2" (Bob)
dados[i][2] = strconv.Itoa(26) // Atualizando a idade para 26
break
}
}
// 3️⃣ Escrever os dados de volta para o arquivo (sobrescrevendo)
file, err = os.Create(arquivo) // Criamos um novo arquivo (sobrescrevendo)
if err != nil {
fmt.Println("Erro ao sobrescrever o arquivo:", err)
return
}
defer file.Close()
writer := csv.NewWriter(file)
err = writer.WriteAll(dados) // Escreve todas as linhas novamente
if err != nil {
fmt.Println("Erro ao escrever no arquivo:", err)
return
}
writer.Flush() // Garante que os dados sejam gravados
fmt.Println("Linha atualizada com sucesso!")
}
No comando acima, começamos lendo todo o arquivo CSV e armazenamos os dados em [][]string
. Em seguida, iteramos sobre os dados para encontrar a linha correspondente ao ID 2
para modificarmos o valor desejado (Idade).
Por fim, reabrimos o arquivo no modo de escrita (os.Create
), e escrevemos os dados atualizados no arquivo, garantindo que sejam gravados com Flush()
.
Adicionando uma linha em um arquivo CSV
Em alguns momentos, será necessário adicionar mais informações ao seu arquivo CSV, e você pode fazer isso da seguinte forma:
package main
import (
"encoding/csv"
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("dados.csv", os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
fmt.Println("Erro ao abrir arquivo:", err)
return
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
writer.Write([]string{"Eve", "35", "Arquiteta"})
fmt.Println("Nova linha adicionada ao CSV!")
}
Removendo uma linha de um arquivo CSV
Para remover uma linha específica de um arquivo CSV em Go, o processo é bem semelhante a sobrescrever uma linha, com a diferença de que, ao invés de modificar o conteúdo de uma linha, você vai remover a linha do slice de dados.
Por exemplo, vamos supor que temos o arquivo dados.csv
com a seguinte estrutura:
ID,Nome,Idade,Profissao
1,Alice,30,Engenheira
2,Bob,25,Desenvolvedor
3,Carlos,40,Designer
Agora, queremos remover a linha onde o ID é "2" (Bob):
package main
import (
"encoding/csv"
"fmt"
"os"
)
func main() {
arquivo := "dados.csv"
// 1️⃣ Abrir o arquivo para leitura
file, err := os.Open(arquivo)
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
reader := csv.NewReader(file)
dados, err := reader.ReadAll() // Lê todo o conteúdo do CSV
if err != nil {
fmt.Println("Erro ao ler CSV:", err)
return
}
// 2️⃣ Remover a linha desejada (ID "2" -> Bob)
var novosDados [][]string
for _, linha := range dados {
if linha[0] != "2" { // Se o ID não for "2", mantemos a linha
novosDados = append(novosDados, linha)
}
}
// 3️⃣ Escrever os dados atualizados de volta para o arquivo
file, err = os.Create(arquivo) // Criamos um novo arquivo (sobrescrevendo)
if err != nil {
fmt.Println("Erro ao sobrescrever o arquivo:", err)
return
}
defer file.Close()
writer := csv.NewWriter(file)
err = writer.WriteAll(novosDados) // Escreve os dados restantes (sem a linha removida)
if err != nil {
fmt.Println("Erro ao escrever no arquivo:", err)
return
}
writer.Flush() // Garante que os dados sejam gravados
fmt.Println("Linha removida com sucesso!")
}
Após a execução do código, o arquivo será sobrescrito e ficará representado da seguinte forma:
ID,Nome,Idade,Profissao
1,Alice,30,Engenheira
3,Carlos,40,Designer
Observação: Se o CSV for grande demais, e você quiser otimizar a memória, você pode processar o arquivo linha por linha ao invés de carregar tudo na memória de uma vez 🙃
Convertendo um CSV para um slice, struct e map
Para abrir um arquivo CSV
e armazená-lo dentro de um slice
, você pode fazer isso da seguinte forma:
package main
import (
"encoding/csv"
"fmt"
"os"
)
func main() {
// Abrir o arquivo CSV
file, err := os.Open("dados.csv")
if err != nil {
fmt.Println("Erro ao abrir arquivo:", err)
return
}
defer file.Close()
// Criar um leitor CSV
reader := csv.NewReader(file)
// Ler todo o conteúdo do arquivo CSV para um slice de slices
records, err := reader.ReadAll()
if err != nil {
fmt.Println("Erro ao ler arquivo CSV:", err)
return
}
// Exibir os dados lidos
for _, record := range records {
fmt.Println(record) // Cada "record" é um slice de strings
}
}
A saída ficará:
[ID Nome Idade Profissao]
[1 Alice 30 Engenheira]
[2 Bob 25 Desenvolvedor]
[3 Carlos 40 Designer]
Para abrir um arquivo CSV
e armazená-lo em uma struct
, use a seguinte lógica abaixo:
package main
import (
"encoding/csv"
"fmt"
"os"
"strconv"
)
// Definir a struct para representar as pessoas
type Pessoa struct {
ID int
Nome string
Idade int
Profissao string
}
func main() {
// Abrir o arquivo CSV
file, err := os.Open("dados.csv")
if err != nil {
fmt.Println("Erro ao abrir arquivo:", err)
return
}
defer file.Close()
// Criar um leitor CSV
reader := csv.NewReader(file)
// Ler todo o conteúdo do arquivo CSV para um slice de slices
records, err := reader.ReadAll()
if err != nil {
fmt.Println("Erro ao ler arquivo CSV:", err)
return
}
// Criar um slice para armazenar os dados convertidos em structs
var pessoas []Pessoa
// Ignorar o cabeçalho e ler as linhas
for i, record := range records {
if i == 0 { // Ignorar o cabeçalho
continue
}
id, _ := strconv.Atoi(record[0]) // Converter string para int
idade, _ := strconv.Atoi(record[2]) // Converter string para int
// Criar e adicionar a struct
pessoa := Pessoa{
ID: id,
Nome: record[1],
Idade: idade,
Profissao: record[3],
}
pessoas = append(pessoas, pessoa)
}
// Exibir as pessoas
for _, p := range pessoas {
fmt.Printf("ID: %d, Nome: %s, Idade: %d, Profissão: %s\n", p.ID, p.Nome, p.Idade, p.Profissao)
}
}
A saída será:
ID: 1, Nome: Alice, Idade: 30, Profissão: Engenheira
ID: 2, Nome: Bob, Idade: 25, Profissão: Desenvolvedor
ID: 3, Nome: Carlos, Idade: 40, Profissão: Designer
Por fim, caso você queria criar um map
com chaves-valores, faça isso da seguinte forma:
package main
import (
"encoding/csv"
"fmt"
"os"
)
func main() {
// Abrir o arquivo CSV
file, err := os.Open("dados.csv")
if err != nil {
fmt.Println("Erro ao abrir arquivo:", err)
return
}
defer file.Close()
// Criar um leitor CSV
reader := csv.NewReader(file)
// Ler todo o conteúdo do arquivo CSV para um slice de slices
records, err := reader.ReadAll()
if err != nil {
fmt.Println("Erro ao ler arquivo CSV:", err)
return
}
// Criar um mapa para armazenar os dados por coluna
dados := make(map[string][]string)
// Ler as colunas (cabeçalho)
cabecalho := records[0]
// Inicializar o mapa com as chaves vazias
for _, coluna := range cabecalho {
dados[coluna] = []string{}
}
// Preencher os dados a partir das linhas
for _, record := range records[1:] {
for i, valor := range record {
dados[cabecalho[i]] = append(dados[cabecalho[i]], valor)
}
}
// Exibir o mapa com os dados por coluna
for chave, valores := range dados {
fmt.Println(chave, ":", valores)
}
}
A saída será:
ID : [1 2 3]
Nome : [Alice Bob Carlos]
Idade : [30 25 40]
Profissao : [Engenheira Desenvolvedor Designer]
Agora que já sabemos como manipular arquivos CSV, vamos aprender a trabalhar com os famosos arquivos XML 😉
O que são arquivos XML?
Arquivos XML (Extensible Markup Language) são arquivos de texto que utilizam uma linguagem de marcação para armazenar e transportar dados.
O XML é projetado para ser legível tanto por humanos quanto por máquinas, e sua principal função é organizar e estruturar dados de maneira hierárquica, utilizando tags (marcadores) que definem os elementos e suas relações.
Vejamos um exemplo de um arquivo XML
:
<pessoa>
<nome>João</nome>
<idade>30</idade>
</pessoa>
Observe que os dados são organizados de forma hierárquica, com elementos aninhados dentro de outros elementos. Isso permite representar estruturas complexas de dados de maneira clara e organizada.
Note também que a estrutura é bastante similar ao HTML
que estamos acostumados a trabalhar na web 😯
Além disso, as tags em XML não são fixas, ou seja, o usuário pode criar suas próprias tags para descrever os dados conforme sua necessidade.
É importante ressaltar que o XML
não depende de nenhum software ou plataforma específica para ser interpretado ou criado, o que o torna uma excelente escolha para a troca de dados entre sistemas diferentes, onde podem ser lidos facilmente por outras linguagens de programação.
Para que serve um arquivo XML?
Um arquivo XML pode ser usados em diferentes cenários, como por exemplo:
Configuração de software: muitos aplicativos usam XML
para armazenar configurações e preferências do usuário.
Troca de dados entre sistemas: XML
é usado para transferir dados entre sistemas diferentes, como é o caso de APIs ou integrações entre servidores.
Documentos e relatórios: algumas plataformas de gerenciamento de conteúdo usam XML
para armazenar dados estruturados de documentos, como livros, artigos ou relatórios.
Dito isso, vamos aprender um pouco mais sobre como nós podemos importar a biblioteca responsável por interpretar arquivos XML em Go 😉
Importando a biblioteca encoding/xml
Por padrão, o GoLang já conta com uma biblioteca que é capaz de fazer a interpretação de um XML.
Essa biblioteca é chamada de encoding/xml
, e pode ser incluída facilmente dentro do seu projeto da seguinte forma:
import "encoding/csv"
Ou, caso você já tenha outras bibliotecas importadas, basta usar assim:
import (
"..."
"encoding/csv"
)
Idealmente, quando lemos um arquivo XML em Go, o formato ideal a ser usado é o map
por conta do seu suporte a chave-valor aninhados.
Mas nada impede que você faça o uso de structs
para isso 😌
Estrutura de um struct em XML
Observe abaixo como criamos uma estrutura de um struct
para comportar a leitura e criação de arquivos XML:
package main
import (
"encoding/xml"
"fmt"
"os"
)
// Definição da estrutura
type Pessoa struct {
XMLName xml.Name `xml:"pessoa"`
Nome string `xml:"nome"`
Idade int `xml:"idade"`
Profissao string `xml:"profissao"`
}
type Pessoas struct {
XMLName xml.Name `xml:"pessoas"`
Lista []Pessoa `xml:"pessoa"`
}
Note que ele funciona de forma similar ao JSON
, a diferença é que usamos a tag xml:
para dizer ao Go que se trata de um arquivo do tipo XML.
Criando e Escrevendo um Arquivo XML
O processo de criação e escrita de um arquivo XML em GoLang, é um processo muito simples, observe:
package main
import (
"encoding/xml"
"fmt"
"os"
)
// Definição da estrutura
type Pessoa struct {
XMLName xml.Name `xml:"pessoa"`
Nome string `xml:"nome"`
Idade int `xml:"idade"`
Profissao string `xml:"profissao"`
}
type Pessoas struct {
XMLName xml.Name `xml:"pessoas"`
Lista []Pessoa `xml:"pessoa"`
}
func main() {
pessoas := Pessoas{
Lista: []Pessoa{
{Nome: "Alice", Idade: 30, Profissao: "Engenheira"},
{Nome: "Bob", Idade: 25, Profissao: "Desenvolvedor"},
},
}
file, err := os.Create("dados.xml")
if err != nil {
fmt.Println("Erro ao criar arquivo:", err)
return
}
defer file.Close()
encoder := xml.NewEncoder(file)
encoder.Indent("", " ")
if err := encoder.Encode(pessoas); err != nil {
fmt.Println("Erro ao escrever XML:", err)
}
fmt.Println("Arquivo XML criado com sucesso!")
}
No código acima, precisamos fazer o uso de duas bibliotecas principais, a primeira responsável pela leitura do XML
(encoding/xml
), e a terceira responsável pela manipulação de arquivos (os
).
Note que eu criei uma representação de um XML por meio de um struct
de forma bem simples, antes de salvar o arquivo com modificações:
pessoas := Pessoas{
Lista: []Pessoa{
{"", "Alice", 30, "Engenheira"},
{"", "Bob", 25, "Desenvolvedor"},
},
}
A saída será:
<pessoas>
<pessoa>
<nome>Alice</nome>
<idade>30</idade>
<profissao>Engenheira</profissao>
</pessoa>
<pessoa>
<nome>Bob</nome>
<idade>25</idade>
<profissao>Desenvolvedor</profissao>
</pessoa>
</pessoas>
Lendo um arquivo XML
Para ler um arquivo XML em Go e convertê-lo em uma estrutura (struct
), também é um processo bem simples.
Supondo que você tenha a seguinte estrutura XML
:
<pessoas>
<pessoa>
<nome>Alice</nome>
<idade>30</idade>
<profissao>Engenheira</profissao>
</pessoa>
<pessoa>
<nome>Bob</nome>
<idade>25</idade>
<profissao>Desenvolvedor</profissao>
</pessoa>
</pessoas>
Você pode utilizar o seguinte código em Go para fazer a leitura:
package main
import (
"encoding/xml"
"fmt"
"os"
)
// Definição das estruturas para o XML
type Pessoa struct {
XMLName xml.Name `xml:"pessoa"`
Nome string `xml:"nome"`
Idade int `xml:"idade"`
Profissao string `xml:"profissao"`
}
type Pessoas struct {
XMLName xml.Name `xml:"pessoas"`
Lista []Pessoa `xml:"pessoa"`
}
func main() {
// Abrir o arquivo XML
file, err := os.Open("dados.xml")
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
// Criar um decoder XML
decoder := xml.NewDecoder(file)
// Criar uma variável para armazenar os dados
var pessoas Pessoas
// Decodificar o XML para a struct
if err := decoder.Decode(&pessoas); err != nil {
fmt.Println("Erro ao decodificar XML:", err)
return
}
// Exibir os dados lidos
for _, pessoa := range pessoas.Lista {
fmt.Printf("Nome: %s, Idade: %d, Profissão: %s\n", pessoa.Nome, pessoa.Idade, pessoa.Profissao)
}
}
Tudo começa quando abrimos o arquivo XML, decodificamos o conteúdo ali existente com xml.Decoder
, ao mesmo tempo que usamos o decoder.Decode(&pessoas)
para converter o XML para a struct
.
Por fim, iteramos sobre pessoas.Lista
para imprimir os valores lidos, onde o resultado impresso no terminal será:
Nome: Alice, Idade: 30, Profissão: Engenheira
Nome: Bob, Idade: 25, Profissão: Desenvolvedor
Incrível, não acha? 🤩
Atualizando um arquivo XML (sobrescrever)
Para atualizar uma linha no arquivo XML (por exemplo, modificar a idade de uma pessoa), você pode fazer isso da seguinte forma:
package main
import (
"encoding/xml"
"fmt"
"os"
)
// Definição das estruturas para o XML
type Pessoa struct {
XMLName xml.Name `xml:"pessoa"`
Nome string `xml:"nome"`
Idade int `xml:"idade"`
Profissao string `xml:"profissao"`
}
type Pessoas struct {
XMLName xml.Name `xml:"pessoas"`
Lista []Pessoa `xml:"pessoa"`
}
func main() {
// Nome do arquivo XML
arquivo := "dados.xml"
// Abrir o arquivo XML para leitura
file, err := os.Open(arquivo)
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
// Criar um decoder XML
decoder := xml.NewDecoder(file)
// Criar a variável para armazenar os dados
var pessoas Pessoas
// Decodificar o XML para a struct
if err := decoder.Decode(&pessoas); err != nil {
fmt.Println("Erro ao decodificar XML:", err)
return
}
// Exibir os dados antes da alteração
fmt.Println("Antes da alteração:")
for _, pessoa := range pessoas.Lista {
fmt.Printf("Nome: %s, Idade: %d, Profissão: %s\n", pessoa.Nome, pessoa.Idade, pessoa.Profissao)
}
// Alterar a idade da pessoa chamada "Alice"
for i := range pessoas.Lista {
if pessoas.Lista[i].Nome == "Alice" {
pessoas.Lista[i].Idade = 35 // Nova idade
}
}
// Criar um novo arquivo para salvar as alterações
file, err = os.Create(arquivo)
if err != nil {
fmt.Println("Erro ao criar arquivo:", err)
return
}
defer file.Close()
// Criar um encoder XML para salvar os dados
encoder := xml.NewEncoder(file)
encoder.Indent("", " ") // Formatação bonita
// Escrever os dados atualizados no XML
if err := encoder.Encode(pessoas); err != nil {
fmt.Println("Erro ao escrever XML:", err)
return
}
// Exibir os dados após a alteração
fmt.Println("\nDepois da alteração:")
for _, pessoa := range pessoas.Lista {
fmt.Printf("Nome: %s, Idade: %d, Profissão: %s\n", pessoa.Nome, pessoa.Idade, pessoa.Profissao)
}
fmt.Println("\nArquivo atualizado com sucesso!")
}
No código acima, lemos o XML, jogamos ele para uma struct
, iteramos sobre a lista Pessoa
, identificamos "Alice"
e mudamos sua idade para 35
.
Por fim, criamos um novo arquivo XML de modo a sobrescrever o anterior.
A saída no terminal será:
Antes da alteração:
Nome: Alice, Idade: 30, Profissão: Engenheira
Nome: Bob, Idade: 25, Profissão: Desenvolvedor
Depois da alteração:
Nome: Alice, Idade: 35, Profissão: Engenheira
Nome: Bob, Idade: 25, Profissão: Desenvolvedor
Arquivo atualizado com sucesso!
Removendo um Registro do XML
Supondo que você queria remover uma registro específico do seu arquivo XML, como por exemplo, excluir o Bob, como podemos fazer isso?
Neste caso, precisamos abrir o arquivo, jogar para uma struct
, fazer a iteração, remover o dado do slice
, e salvar de novo (sobrescrever) no mesmo arquivo, observe:
package main
import (
"encoding/xml"
"fmt"
"os"
)
// Definição das estruturas para o XML
type Pessoa struct {
XMLName xml.Name `xml:"pessoa"`
Nome string `xml:"nome"`
Idade int `xml:"idade"`
Profissao string `xml:"profissao"`
}
type Pessoas struct {
XMLName xml.Name `xml:"pessoas"`
Lista []Pessoa `xml:"pessoa"`
}
func main() {
// Nome do arquivo XML
arquivo := "dados.xml"
// Abrir o arquivo XML para leitura
file, err := os.Open(arquivo)
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
// Criar um decoder XML
decoder := xml.NewDecoder(file)
// Criar a variável para armazenar os dados
var pessoas Pessoas
// Decodificar o XML para a struct
if err := decoder.Decode(&pessoas); err != nil {
fmt.Println("Erro ao decodificar XML:", err)
return
}
// Exibir os dados antes da remoção
fmt.Println("Antes da remoção:")
for _, pessoa := range pessoas.Lista {
fmt.Printf("Nome: %s, Idade: %d, Profissão: %s\n", pessoa.Nome, pessoa.Idade, pessoa.Profissao)
}
// Nome da pessoa a ser removida
nomeParaRemover := "Bob"
// Criar uma nova lista sem a pessoa desejada
var novaLista []Pessoa
for _, pessoa := range pessoas.Lista {
if pessoa.Nome != nomeParaRemover {
novaLista = append(novaLista, pessoa)
}
}
// Atualizar a lista de pessoas
pessoas.Lista = novaLista
// Criar um novo arquivo para salvar as alterações
file, err = os.Create(arquivo)
if err != nil {
fmt.Println("Erro ao criar arquivo:", err)
return
}
defer file.Close()
// Criar um encoder XML para salvar os dados
encoder := xml.NewEncoder(file)
encoder.Indent("", " ") // Formatação bonita
// Escrever os dados atualizados no XML
if err := encoder.Encode(pessoas); err != nil {
fmt.Println("Erro ao escrever XML:", err)
return
}
// Exibir os dados após a remoção
fmt.Println("\nDepois da remoção:")
for _, pessoa := range pessoas.Lista {
fmt.Printf("Nome: %s, Idade: %d, Profissão: %s\n", pessoa.Nome, pessoa.Idade, pessoa.Profissao)
}
fmt.Println("\nRegistro removido e arquivo atualizado com sucesso!")
}
Tudo o que fizemos acima, foi remover o Bob da lista, sobrescrevendo um novo arquivo XML 😋
Adicionando um novo registro ao XML
Seguindo a lógica anterior, você pode facilmente adicionar um novo registro ao seu XML, bastando abrir o arquivo, converter para struct
, adicionar um novo elemento a lista e realizar a sua sobrescrição:
package main
import (
"encoding/xml"
"fmt"
"os"
)
// Definição das estruturas XML
type Pessoa struct {
XMLName xml.Name `xml:"pessoa"`
Nome string `xml:"nome"`
Idade int `xml:"idade"`
Profissao string `xml:"profissao"`
}
type Pessoas struct {
XMLName xml.Name `xml:"pessoas"`
Lista []Pessoa `xml:"pessoa"`
}
func main() {
// Nome do arquivo XML
arquivo := "dados.xml"
// Abrir o arquivo XML para leitura
file, err := os.Open(arquivo)
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
// Criar um decoder XML
decoder := xml.NewDecoder(file)
// Criar a variável para armazenar os dados
var pessoas Pessoas
// Decodificar o XML para a struct
if err := decoder.Decode(&pessoas); err != nil {
fmt.Println("Erro ao decodificar XML:", err)
return
}
// Exibir os dados antes da adição
fmt.Println("Antes da adição:")
for _, pessoa := range pessoas.Lista {
fmt.Printf("Nome: %s, Idade: %d, Profissão: %s\n", pessoa.Nome, pessoa.Idade, pessoa.Profissao)
}
// Criar um novo registro
novoRegistro := Pessoa{
Nome: "Carlos",
Idade: 40,
Profissao: "Analista",
}
// Adicionar o novo registro à lista existente
pessoas.Lista = append(pessoas.Lista, novoRegistro)
// Criar um novo arquivo para salvar as alterações
file, err = os.Create(arquivo)
if err != nil {
fmt.Println("Erro ao criar arquivo:", err)
return
}
defer file.Close()
// Criar um encoder XML para salvar os dados
encoder := xml.NewEncoder(file)
encoder.Indent("", " ") // Formatação bonita
// Escrever os dados atualizados no XML
if err := encoder.Encode(pessoas); err != nil {
fmt.Println("Erro ao escrever XML:", err)
return
}
// Exibir os dados após a adição
fmt.Println("\nDepois da adição:")
for _, pessoa := range pessoas.Lista {
fmt.Printf("Nome: %s, Idade: %d, Profissão: %s\n", pessoa.Nome, pessoa.Idade, pessoa.Profissao)
}
fmt.Println("\nNovo registro adicionado e arquivo atualizado com sucesso!")
}
A saída será:
Antes da adição:
Nome: Alice, Idade: 30, Profissão: Engenheira
Nome: Bob, Idade: 25, Profissão: Desenvolvedor
Depois da adição:
Nome: Alice, Idade: 30, Profissão: Engenheira
Nome: Bob, Idade: 25, Profissão: Desenvolvedor
Nome: Carlos, Idade: 40, Profissão: Analista
Novo registro adicionado e arquivo atualizado com sucesso!
Interpretando XML desconhecido
Haverá momentos em que não saberemos a estrutura exata de um XML, como é o caso de um XML vindo de uma API ou de um arquivo desconhecido.
Em casos como esses, nós ainda podemos fazer o uso do pacote encoding/xml
de forma genérica.
Existem algumas abordagens diferentes que podemos utilizar para fazermos a leitura de tais arquivos:
- Usar
map[string]string
para capturar atributos e valores de forma flexível, - Usar
interface{}
exml.Decoder
para ler o XML de maneira dinâmica, - Usar uma estrutura parcialmente definida para capturar apenas os dados necessários.
Dito isso, vamos começar pela estratégia de map
😉
Lendo um XML desconhecido com map
A primeira estratégia envolve a leitura de um XML na qual não conhecemos suas chaves por meio de um map[string]string
.
Supondo que você tenha um arquivo desconhecido chamado de produto.xml
:
<produto id="123" categoria="eletrônicos">
Smartphone Ultra 5G
</produto>
Você poderia lê-lo da seguinte forma:
package main
import (
"encoding/xml"
"fmt"
"os"
)
// Estrutura genérica para armazenar qualquer elemento XML
type Elemento struct {
XMLName xml.Name `xml:""`
Atributos map[string]string `xml:",any,attr"`
Conteudo string `xml:",chardata"`
}
func main() {
// Abrir o arquivo XML desconhecido
arquivo := "produto.xml"
file, err := os.Open(arquivo)
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
// Criar um decoder XML
decoder := xml.NewDecoder(file)
// Ler o XML de forma genérica
var elemento Elemento
if err := decoder.Decode(&elemento); err != nil {
fmt.Println("Erro ao decodificar XML:", err)
return
}
// Exibir os dados lidos
fmt.Printf("Elemento: %s\n", elemento.XMLName.Local)
fmt.Println("Atributos:")
for k, v := range elemento.Atributos {
fmt.Printf(" %s = %s\n", k, v)
}
fmt.Println("Conteúdo:", elemento.Conteudo)
}
O comando acima, permite que você consiga extrair qualquer elemento com atributos e textos diferentes, sem precisar criar uma struct
especializada contendo todos os campos, ou saber a estrutura exata do elemento.
A saída será:
Elemento: produto
Atributos:
id = 123
categoria = eletrônicos
Conteúdo: Smartphone Ultra 5G
Lendo um XML desconhecido com o xml.Decoder (stream)
A segunda estratégia envolve o uso do comando xml.Decoder.Token()
, e é uma excelente alternativa quando estamos trabalhando com um XML muito grande, ou que precisa ser lido de forma dinâmica.
Supondo que você tenha um XML chamado produto.xml
com a seguinte estrutura:
<loja>
<produto id="101">
<nome>Notebook Gamer</nome>
<preco moeda="USD">1200</preco>
</produto>
<produto id="102">
<nome>Smartphone</nome>
<preco moeda="BRL">3000</preco>
</produto>
</loja>
Você pode usar a lógica abaixo para fazer a sua leitura:
package main
import (
"encoding/xml"
"fmt"
"os"
)
func main() {
arquivo := "produto.xml"
file, err := os.Open(arquivo)
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
decoder := xml.NewDecoder(file)
// Percorrer os tokens do XML
for {
token, err := decoder.Token()
if err != nil {
break // Fim do arquivo
}
// Identificar o tipo do token (elemento de abertura, fechamento, conteúdo etc.)
switch elemento := token.(type) {
case xml.StartElement:
fmt.Printf("Início do elemento: %s\n", elemento.Name.Local)
for _, attr := range elemento.Attr {
fmt.Printf(" Atributo: %s = %s\n", attr.Name.Local, attr.Value)
}
case xml.CharData:
conteudo := string(elemento)
if len(conteudo) > 0 {
fmt.Println(" Conteúdo:", conteudo)
}
case xml.EndElement:
fmt.Printf("Fim do elemento: %s\n", elemento.Name.Local)
}
}
}
A saída no terminal será a seguinte:
Início do elemento: loja
Início do elemento: produto
Atributo: id = 101
Início do elemento: nome
Conteúdo: Notebook Gamer
Fim do elemento: nome
Início do elemento: preco
Atributo: moeda = USD
Conteúdo: 1200
Fim do elemento: preco
Fim do elemento: produto
Início do elemento: produto
Atributo: id = 102
Início do elemento: nome
Conteúdo: Smartphone
Fim do elemento: nome
Início do elemento: preco
Atributo: moeda = BRL
Conteúdo: 3000
Fim do elemento: preco
Fim do elemento: produto
Fim do elemento: loja
Lendo um XML desconhecido por meio de uma struct parcialmente definida
A última e terceira estratégia requer que você conheça uma parte da estrutura do XML
.
Vejamos como isso pode ser feito, ainda usando a estrutura existente em produto.xml
:
package main
import (
"encoding/xml"
"fmt"
"os"
)
// Estrutura conhecida
type Loja struct {
XMLName xml.Name `xml:"loja"`
Produtos []Produto `xml:"produto"`
}
type Produto struct {
XMLName xml.Name `xml:"produto"`
ID string `xml:"id,attr"`
Nome string `xml:"nome"`
Preco Preco `xml:"preco"`
}
type Preco struct {
XMLName xml.Name `xml:"preco"`
Moeda string `xml:"moeda,attr"`
Valor string `xml:",chardata"`
}
func main() {
arquivo := "produto-2.xml"
file, err := os.Open(arquivo)
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
var loja Loja
decoder := xml.NewDecoder(file)
if err := decoder.Decode(&loja); err != nil {
fmt.Println("Erro ao decodificar XML:", err)
return
}
// Exibir produtos extraídos
fmt.Println("Produtos extraídos:")
for _, p := range loja.Produtos {
fmt.Printf("ID: %s, Nome: %s, Preço: %s %s\n", p.ID, p.Nome, p.Preco.Moeda, p.Preco.Valor)
}
}
A saída no terminal será a seguinte:
Produtos extraídos:
ID: 101, Nome: Notebook Gamer, Preço: USD 1200
ID: 102, Nome: Smartphone, Preço: BRL 3000
Essa estratégia é extremamente útil quando você sabe parte da estrutura de um XML, ao mesmo tempo que você não precisa definir todos os seus índices em uma struct
😁
Repositório da lição
Todos os arquivos relacionados com esta lição, podem ser encontrados nos seguintes repositórios abaixo:
Conclusão
Nesta lição, você aprendeu a manipular arquivos CSV e XML usando as bibliotecas encoding/csv
e encoding/xml
.
Agora você está pronto para interpretar qualquer um desses arquivos em Go! 😃🚀