Introdução
CSV (Comma-Separated Values) é um dos formatos mais comuns para dados tabulares. Apesar de parecer simples, parsear CSV corretamente requer lidar com campos entre aspas, caracteres de escape e diferentes separadores. Nesta receita, você aprenderá a implementar um parser CSV funcional em Zig.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
Parser CSV Básico
Parsear CSV simples (sem campos entre aspas):
const std = @import("std");
fn parsearLinhaCSV(allocator: std.mem.Allocator, linha: []const u8, delimitador: u8) ![][]const u8 {
var campos = std.ArrayList([]const u8).init(allocator);
errdefer campos.deinit();
var iter = std.mem.splitScalar(u8, linha, delimitador);
while (iter.next()) |campo| {
const trimmed = std.mem.trim(u8, campo, " ");
try campos.append(trimmed);
}
return campos.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const csv =
\\nome,idade,cidade
\\Maria,28,São Paulo
\\João,35,Rio de Janeiro
\\Ana,22,Belo Horizonte
\\Pedro,31,Curitiba
;
var linhas = std.mem.splitScalar(u8, csv, '\n');
// Cabeçalho
if (linhas.next()) |header| {
const campos = try parsearLinhaCSV(allocator, header, ',');
defer allocator.free(campos);
std.debug.print("Colunas: ", .{});
for (campos, 0..) |c, i| {
if (i > 0) std.debug.print(" | ", .{});
std.debug.print("{s}", .{c});
}
std.debug.print("\n{s}\n", .{"-" ** 40});
}
// Dados
while (linhas.next()) |linha| {
if (linha.len == 0) continue;
const campos = try parsearLinhaCSV(allocator, linha, ',');
defer allocator.free(campos);
for (campos, 0..) |c, i| {
if (i > 0) std.debug.print(" | ", .{});
std.debug.print("{s}", .{c});
}
std.debug.print("\n", .{});
}
}
Saída esperada
Colunas: nome | idade | cidade
----------------------------------------
Maria | 28 | São Paulo
João | 35 | Rio de Janeiro
Ana | 22 | Belo Horizonte
Pedro | 31 | Curitiba
CSV para Structs Tipados
const std = @import("std");
const Funcionario = struct {
nome: []const u8,
cargo: []const u8,
salario: f64,
};
fn parsearFuncionario(linha: []const u8) !Funcionario {
var iter = std.mem.splitScalar(u8, linha, ',');
const nome = std.mem.trim(u8, iter.next() orelse return error.CampoFaltando, " ");
const cargo = std.mem.trim(u8, iter.next() orelse return error.CampoFaltando, " ");
const salario_str = std.mem.trim(u8, iter.next() orelse return error.CampoFaltando, " ");
const salario = try std.fmt.parseFloat(f64, salario_str);
return .{
.nome = nome,
.cargo = cargo,
.salario = salario,
};
}
pub fn main() !void {
const csv =
\\Alice,Desenvolvedora,8500.00
\\Bob,Designer,7200.00
\\Carlos,Gerente,12000.00
\\Diana,Analista,6800.00
;
var linhas = std.mem.splitScalar(u8, csv, '\n');
var total_salario: f64 = 0;
var count: u32 = 0;
std.debug.print("{s:<15} {s:<20} {s:>10}\n", .{ "Nome", "Cargo", "Salário" });
std.debug.print("{s}\n", .{"-" ** 47});
while (linhas.next()) |linha| {
if (linha.len == 0) continue;
if (parsearFuncionario(linha)) |func| {
std.debug.print("{s:<15} {s:<20} R${d:>8.2}\n", .{
func.nome, func.cargo, func.salario,
});
total_salario += func.salario;
count += 1;
} else |err| {
std.debug.print("Erro ao parsear linha: {}\n", .{err});
}
}
std.debug.print("{s}\n", .{"-" ** 47});
std.debug.print("{s:<35} R${d:>8.2}\n", .{ "Total:", total_salario });
std.debug.print("{s:<35} R${d:>8.2}\n", .{ "Média:", total_salario / @as(f64, @floatFromInt(count)) });
}
Gerar CSV a partir de Dados
const std = @import("std");
const Produto = struct {
nome: []const u8,
preco: f64,
estoque: u32,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const produtos = [_]Produto{
.{ .nome = "Teclado", .preco = 199.90, .estoque = 45 },
.{ .nome = "Mouse", .preco = 89.90, .estoque = 120 },
.{ .nome = "Monitor", .preco = 1299.00, .estoque = 15 },
.{ .nome = "Headset", .preco = 349.90, .estoque = 30 },
};
// Gerar CSV
var csv = std.ArrayList(u8).init(allocator);
defer csv.deinit();
const writer = csv.writer();
// Cabeçalho
try writer.writeAll("nome,preco,estoque\n");
// Dados
for (&produtos) |produto| {
try writer.print("{s},{d:.2},{d}\n", .{ produto.nome, produto.preco, produto.estoque });
}
std.debug.print("CSV gerado:\n{s}", .{csv.items});
}
CSV com Campos entre Aspas
const std = @import("std");
fn parsearCampoCSV(allocator: std.mem.Allocator, input: []const u8) !struct { campo: []u8, rest: []const u8 } {
var campo = std.ArrayList(u8).init(allocator);
errdefer campo.deinit();
var pos: usize = 0;
if (pos < input.len and input[pos] == '"') {
// Campo entre aspas
pos += 1;
while (pos < input.len) {
if (input[pos] == '"') {
if (pos + 1 < input.len and input[pos + 1] == '"') {
try campo.append('"');
pos += 2;
} else {
pos += 1;
break;
}
} else {
try campo.append(input[pos]);
pos += 1;
}
}
} else {
// Campo simples
while (pos < input.len and input[pos] != ',') {
try campo.append(input[pos]);
pos += 1;
}
}
// Pular vírgula
if (pos < input.len and input[pos] == ',') pos += 1;
return .{
.campo = try campo.toOwnedSlice(),
.rest = input[pos..],
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const linhas = [_][]const u8{
\\simples,campo,normal
,
\\"com espaço","outro campo",valor
,
\\"aspas ""dentro""","vírgula, no campo",fim
,
};
for (linhas) |linha| {
if (linha.len == 0) continue;
std.debug.print("Entrada: {s}\n", .{linha});
std.debug.print("Campos: ", .{});
var rest = linha;
var primeiro = true;
while (rest.len > 0) {
const resultado = try parsearCampoCSV(allocator, rest);
defer allocator.free(resultado.campo);
rest = resultado.rest;
if (!primeiro) std.debug.print(" | ", .{});
std.debug.print("[{s}]", .{resultado.campo});
primeiro = false;
}
std.debug.print("\n\n", .{});
}
}
Dicas e Boas Práticas
Cuidado com campos entre aspas: CSV real frequentemente tem campos com vírgulas e quebras de linha dentro de aspas.
Valide os dados: Sempre trate erros ao converter strings para números.
Considere o encoding: Dados em português podem ter caracteres UTF-8 que ocupam mais bytes.
Use ArenaAllocator: Para processar CSV grande, arena simplifica a limpeza de memória.
Receitas Relacionadas
- Ler Conteúdo de Arquivo - Ler arquivo CSV
- Ler Arquivo Linha por Linha - CSV grande
- Dividir (Split) Strings - Dividir campos
- Converter String para Número - Converter campos