use crate::domain::errors::{ErroArquivo, ResumoAvisos}; use encoding_rs::WINDOWS_1252; use std::path::Path; const LIMITE_BYTES: u64 = 50 * 1024 * 1024; /// Resultado da leitura de um arquivo CSV. pub struct ResultadoCsv { /// Linhas de dados (já sem o cabeçalho), cada linha é um vetor de strings. pub linhas: Vec>, /// Avisos coletados durante a leitura. pub avisos: ResumoAvisos, } /// Lê um arquivo CSV e retorna as linhas de dados (sem o cabeçalho). /// /// # Parâmetros /// - `caminho`: caminho do arquivo /// - `delimitador`: caractere delimitador (`,`, `;` ou `\t`) /// - `encoding`: "utf-8" ou "windows-1252" /// - `linha_cabecalho`: número da linha do cabeçalho (base 1). 0 = sem cabeçalho. pub fn ler_csv( caminho: &Path, delimitador: u8, encoding: &str, linha_cabecalho: usize, ) -> Result { // Verificar tamanho let metadata = std::fs::metadata(caminho) .map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?; if metadata.len() > LIMITE_BYTES { return Err(ErroArquivo::TamanhoExcedido(metadata.len())); } // Ler conteúdo bruto let bytes = std::fs::read(caminho) .map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?; // Decodificar encoding let conteudo = match encoding.to_lowercase().as_str() { "windows-1252" | "latin-1" | "iso-8859-1" => { let (decoded, _, _) = WINDOWS_1252.decode(&bytes); decoded.into_owned() } _ => { String::from_utf8(bytes) .map_err(|e| ErroArquivo::ErroLeitura(format!("Encoding inválido: {}", e)))? } }; let mut avisos = ResumoAvisos::default(); let mut linhas_dados: Vec> = Vec::new(); let mut numero_linha_arquivo: usize = 0; let mut reader = csv::ReaderBuilder::new() .delimiter(delimitador) .has_headers(false) .flexible(true) .from_reader(conteudo.as_bytes()); for resultado in reader.records() { numero_linha_arquivo += 1; // Pular linhas antes ou na linha do cabeçalho if linha_cabecalho > 0 && numero_linha_arquivo <= linha_cabecalho { continue; } match resultado { Ok(record) => { let campos: Vec = record.iter().map(|s| s.to_string()).collect(); // Ignorar linhas completamente em branco if campos.iter().all(|s| s.trim().is_empty()) { continue; } linhas_dados.push(campos); } Err(_) => { avisos.linhas_malformadas += 1; } } } Ok(ResultadoCsv { linhas: linhas_dados, avisos, }) } /// Retorna as primeiras `n` linhas brutas do CSV (sem pular cabeçalho). /// Usado exclusivamente para pré-visualização na UI. pub fn preview_csv( caminho: &Path, delimitador: u8, encoding: &str, n: usize, ) -> Result>, ErroArquivo> { let bytes = std::fs::read(caminho) .map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?; let conteudo = match encoding.to_lowercase().as_str() { "windows-1252" | "latin-1" | "iso-8859-1" => { let (decoded, _, _) = WINDOWS_1252.decode(&bytes); decoded.into_owned() } _ => String::from_utf8(bytes) .map_err(|e| ErroArquivo::ErroLeitura(format!("Encoding inválido: {}", e)))?, }; let mut reader = csv::ReaderBuilder::new() .delimiter(delimitador) .has_headers(false) .flexible(true) .from_reader(conteudo.as_bytes()); let linhas = reader .records() .take(n) .filter_map(|r| r.ok()) .map(|r| r.iter().map(|s| s.to_string()).collect()) .collect(); Ok(linhas) }