1#![doc = include_str!("../README.md")]
2
3use std::borrow::Cow;
4use unicode_normalization::UnicodeNormalization;
5
6use itertools::Itertools;
7use regex::{Regex, RegexSet};
8
9pub mod bairro;
10pub mod cep;
11pub mod complemento;
12pub mod estado;
13pub mod logradouro;
14pub mod metaphone;
15pub mod municipio;
16pub mod numero;
17pub mod numero_extenso;
18pub mod separador_endereco;
19pub mod tipo_logradouro;
20
21#[derive(Debug, PartialEq, Default)]
23pub struct Endereco {
24 pub logradouro: Option<String>,
25 pub numero: Option<String>,
26 pub complemento: Option<String>,
27 pub localidade: Option<String>,
28}
29
30impl Endereco {
31 pub fn logradouro_padronizado(&self) -> Option<String> {
33 self.logradouro
34 .as_ref()
35 .map(|x| padronizar_logradouros(x.as_str()))
36 }
37
38 pub fn numero_padronizado(&self) -> Option<String> {
40 self.numero.as_ref().map(|x| padronizar_numeros(x.as_str()))
41 }
42
43 pub fn complemento_padronizado(&self) -> Option<String> {
45 self.complemento
46 .as_ref()
47 .map(|x| padronizar_complementos(x.as_str()))
48 }
49
50 pub fn localidade_padronizada(&self) -> Option<String> {
52 self.localidade
53 .as_ref()
54 .map(|x| padronizar_bairros(x.as_str()))
55 }
56
57 pub fn endereco_padronizado(&self) -> Endereco {
60 Endereco {
61 logradouro: self.logradouro_padronizado(),
62 numero: self.numero_padronizado(),
63 complemento: self.complemento_padronizado(),
64 localidade: self.localidade_padronizada(),
65 }
66 }
67
68 pub fn formatar(&self) -> String {
71 [
72 &self.logradouro,
73 &self.numero,
74 &self.complemento,
75 &self.localidade,
76 ]
77 .iter()
78 .filter_map(|opt| opt.as_deref())
79 .map(|x| x.trim())
80 .join(", ")
81 }
82}
83
84#[derive(Debug)]
86pub struct ParSubstituicao {
87 regexp: Regex,
88 substituicao: String,
89 regexp_ignorar: Option<Regex>,
90}
91
92impl ParSubstituicao {
93 fn new(regex: &str, substituicao: &str, regex_ignorar: Option<&str>) -> Self {
94 ParSubstituicao {
95 regexp: Regex::new(regex).unwrap(),
96 substituicao: substituicao.to_uppercase().to_string(),
97 regexp_ignorar: regex_ignorar.map(|r| Regex::new(r).unwrap()),
98 }
99 }
100}
101
102#[derive(Default)]
109pub struct Padronizador {
110 substituicoes: Vec<ParSubstituicao>,
111 grupo_regex: RegexSet,
112}
113
114impl Padronizador {
115 pub fn adicionar_pares(&mut self, pares: &[&[Option<&str>]]) {
129 for p in pares
130 .iter()
131 .map(|p| p.iter().filter_map(|i| i.as_ref()).collect::<Vec<_>>())
132 {
133 if p.is_empty() {
134 continue;
135 }
136 if p.len() == 1 {
137 self.adicionar(p[0], "");
138 }
139 if p.len() == 2 {
140 self.adicionar(p[0], p[1]);
141 }
142 if p.len() >= 3 {
143 self.adicionar_com_ignorar(p[0], p[1], p[2]);
144 }
145 }
146 self.preparar();
147 }
148
149 pub fn adicionar_vetores(
163 &mut self,
164 regexes: &[&str],
165 substituicao: &[&str],
166 regex_ignorar: &[Option<&str>],
167 ) {
168 assert!(
169 regexes.len() == substituicao.len() && regexes.len() == regex_ignorar.len(),
170 "O tamanho dos três vetores devem ser iguais."
171 );
172
173 for ((r, s), i) in regexes.iter().zip(substituicao).zip(regex_ignorar) {
174 if let Some(regex_ignorar) = i {
175 self.adicionar_com_ignorar(r, s, regex_ignorar);
176 } else {
177 self.adicionar(r, s);
178 }
179 }
180 self.preparar();
181 }
182
183 pub fn adicionar(&mut self, regex: &str, substituicao: &str) -> &mut Self {
191 self.substituicoes
192 .push(ParSubstituicao::new(regex, substituicao, None));
193 self
194 }
195
196 pub fn adicionar_com_ignorar(
208 &mut self,
209 regex: &str,
210 substituicao: &str,
211 regexp_ignorar: &str,
212 ) -> &mut Self {
213 self.substituicoes.push(ParSubstituicao::new(
214 regex,
215 substituicao,
216 Some(regexp_ignorar),
217 ));
218 self
219 }
220
221 pub fn preparar(&mut self) {
227 let regexes: Vec<&str> = self
228 .substituicoes
229 .iter()
230 .map(|par| par.regexp.as_str())
231 .collect();
232
233 self.grupo_regex = RegexSet::new(regexes).unwrap();
234 }
235
236 pub fn padronizar(&self, valor: &str) -> String {
245 return self.padronizar_cow(valor).to_string();
246 }
247
248 fn padronizar_cow<'a>(&self, valor: &'a str) -> Cow<'a, str> {
251 let mut preproc = normalizar(valor);
252 let mut ultimo_idx: Option<usize> = None;
253
254 while self.grupo_regex.is_match(&preproc) {
255 let idx_substituicao = self
256 .grupo_regex
257 .matches(&preproc)
258 .iter()
259 .find(|idx| ultimo_idx.is_none_or(|ultimo| *idx > ultimo));
260
261 let Some(idx) = idx_substituicao else {
262 break;
263 };
264
265 ultimo_idx = idx_substituicao;
266 let par = &self.substituicoes[idx];
267
268 if par
271 .regexp_ignorar
272 .as_ref()
273 .map(|r| r.is_match(&preproc))
274 .unwrap_or(false)
275 {
276 continue;
277 }
278
279 let novo_valor = par.regexp.replace_all(&preproc, par.substituicao.as_str());
280 preproc = match novo_valor {
283 Cow::Owned(novo) => Cow::Owned(novo),
284 Cow::Borrowed(_) => preproc, };
286 }
287
288 preproc
289 }
290
291 pub fn obter_pares(&self) -> Vec<(&str, &str, Option<&str>)> {
298 self.substituicoes
299 .iter()
300 .map(|par| {
301 (
302 par.regexp.as_str(),
303 par.substituicao.as_str(),
304 par.regexp_ignorar.as_ref().map(Regex::as_str),
305 )
306 })
307 .collect()
308 }
309
310 pub fn obter_vetores(&self) -> (Vec<&str>, Vec<&str>, Vec<Option<&str>>) {
316 let regex = self
317 .substituicoes
318 .iter()
319 .map(|par| par.regexp.as_str())
320 .collect();
321 let subst = self
322 .substituicoes
323 .iter()
324 .map(|par| par.substituicao.as_str())
325 .collect();
326 let ignorar = self
327 .substituicoes
328 .iter()
329 .map(|par| par.regexp_ignorar.as_ref().map(Regex::as_str))
330 .collect();
331 (regex, subst, ignorar)
332 }
333}
334
335pub fn normalizar(valor: &str) -> Cow<'_, str> {
346 let valor = valor.trim();
347
348 if valor.is_ascii() {
349 if valor
350 .bytes()
351 .all(|c| !c.is_ascii_alphabetic() || c.is_ascii_uppercase())
352 {
353 return Cow::Borrowed(valor.trim());
354 }
355 return Cow::Owned(valor.trim().to_ascii_uppercase());
356 }
357
358 valor
359 .nfkd()
360 .filter(|c| c.is_ascii())
361 .map(|c| c.to_ascii_uppercase())
362 .collect()
363}
364
365pub use bairro::padronizar_bairros;
366pub use cep::padronizar_cep;
367pub use cep::padronizar_cep_leniente;
368pub use cep::padronizar_cep_numerico;
369pub use complemento::padronizar_complementos;
370pub use estado::padronizar_estados_para_codigo;
371pub use estado::padronizar_estados_para_nome;
372pub use estado::padronizar_estados_para_sigla;
373pub use logradouro::padronizar_logradouros;
374pub use municipio::padronizar_municipios;
375pub use numero::padronizar_numeros;
376pub use numero::padronizar_numeros_para_int;
377pub use numero::padronizar_numeros_para_string;
378pub use tipo_logradouro::padronizar_tipo_logradouro;
379
380#[cfg(feature = "experimental")]
381pub use separador_endereco::padronizar_endereco_bruto;
382
383#[cfg(feature = "experimental")]
384pub use separador_endereco::separar_endereco;
385
386pub fn obter_padronizador_por_tipo(tipo: &str) -> Result<fn(&str) -> String, &str> {
389 match tipo {
390 "logradouro" | "logr" => Ok(padronizar_logradouros),
391 "tipo_logradouro" | "tipo_logr" => Ok(padronizar_tipo_logradouro),
392 "numero" | "num" => Ok(padronizar_numeros),
393 "bairro" => Ok(padronizar_bairros),
394 "complemento" | "comp" => Ok(padronizar_complementos),
395 "estado" => Ok(|x| padronizar_estados_para_sigla(x).to_string()),
396 "estado_nome" => Ok(|x| padronizar_estados_para_nome(x).to_string()),
397 "estado_codigo" => Ok(|x| padronizar_estados_para_codigo(x).to_string()),
398 "municipio" | "mun" => Ok(padronizar_municipios),
399 "cep" => Ok(|cep| padronizar_cep(cep).unwrap_or("".to_string())),
400 "cep_leniente" => Ok(padronizar_cep_leniente),
401 "metaphone" => Ok(metaphone::metaphone),
402
403 #[cfg(feature = "experimental")]
404 "completo" => Ok(padronizar_endereco_bruto),
405
406 #[cfg(feature = "experimental")]
407 "separar" => Ok(|val| format!("{:?}", separar_endereco(val))),
408
409 #[cfg(feature = "experimental")]
410 "separar_padronizar" => {
411 Ok(|val| format!("{:?}", separar_endereco(val).endereco_padronizado()))
412 }
413
414 _ => Err("Nenhum padronizador encontrado"),
415 }
416}
417
418#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn test_obter_pares_e_vetores_vazios() {
426 let pad = Padronizador::default();
427 assert_eq!(pad.obter_pares(), vec![]);
428 assert_eq!(pad.obter_vetores(), (vec![], vec![], vec![]));
429 }
430
431 #[test]
432 fn test_adicionar_pares() {
433 let mut pad = Padronizador::default();
434 pad.adicionar_pares(&[
435 &[Some("R"), Some("RUA")], &[Some("AV"), Some("AVENIDA"), Some("COMERCIAL")], &[Some("ESC"), None, Some("ESCOLA")], &[None], ]);
440
441 let pares = pad.obter_pares();
442 assert_eq!(
443 pares,
444 vec![
445 ("R", "RUA", None),
446 ("AV", "AVENIDA", Some("COMERCIAL")),
447 ("ESC", "ESCOLA", None),
448 ]
449 );
450
451 let (regex, subst, ignorar) = pad.obter_vetores();
452 assert_eq!(regex, vec!["R", "AV", "ESC"]);
453 assert_eq!(subst, vec!["RUA", "AVENIDA", "ESCOLA"]);
454 assert_eq!(ignorar, vec![None, Some("COMERCIAL"), None]);
455 }
456
457 #[test]
458 fn test_adicionar_vetores() {
459 let mut pad = Padronizador::default();
460 pad.adicionar_vetores(
461 &["NUM", "R", "AV"],
462 &["NUMERO", "RUA", "AVENIDA"],
463 &[None, Some("R$"), Some("AVENIDA COMERCIAL")],
464 );
465
466 let pares = pad.obter_pares();
467 assert_eq!(
468 pares,
469 vec![
470 ("NUM", "NUMERO", None),
471 ("R", "RUA", Some("R$")),
472 ("AV", "AVENIDA", Some("AVENIDA COMERCIAL")),
473 ]
474 );
475
476 let (regex, subst, ignorar) = pad.obter_vetores();
477 assert_eq!(regex, vec!["NUM", "R", "AV"]);
478 assert_eq!(subst, vec!["NUMERO", "RUA", "AVENIDA"]);
479 assert_eq!(ignorar, vec![None, Some("R$"), Some("AVENIDA COMERCIAL")]);
480 }
481
482 #[test]
483 #[should_panic(expected = "O tamanho dos três vetores devem ser iguais.")]
484 fn test_adicionar_vetores_tamanho_diferente() {
485 let mut pad = Padronizador::default();
486 pad.adicionar_vetores(&["a"], &["b"], &[Some("x"), Some("y")]);
487 }
488}