Manipulando CSV e XML em Go

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{} e xml.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! 😃🚀

Criadores de Conteúdo

Foto do William Lima
William Lima
Fundador da Micilini

Inventor nato, escreve conteudos de programação para o portal da micilini.

Torne-se um MIC 🤖

Mais de 100 mic's já estão conectados na plataforma.