use super::get_structure;
use crate::xml_utils::escape_xml;
use std::collections::HashMap;
pub(super) type Fields = HashMap<String, String>;
pub(super) fn fields_to_std(fields: &[&str], struct_def: &str) -> Fields {
let struct_fields: Vec<&str> = struct_def.split('|').collect();
let mut std = Fields::new();
let len = struct_fields.len().saturating_sub(1);
for (i, name) in struct_fields.iter().enumerate().take(len).skip(1) {
let data = fields.get(i).copied().unwrap_or("");
if !name.is_empty() && !data.is_empty() {
std.insert(name.to_string(), data.to_string());
}
}
std
}
pub(super) fn xml_tag(name: &str, content: &str) -> String {
format!("<{name}>{content}</{name}>")
}
pub(super) fn add_child(arr: &mut Vec<String>, name: &str, value: Option<&str>) {
if let Some(v) = value {
arr.push(format!("<{name}>{}</{name}>", escape_xml(v)));
}
}
pub(super) fn add_child_str(arr: &mut Vec<String>, name: &str, value: &str) {
arr.push(format!("<{name}>{}</{name}>", escape_xml(value)));
}
pub(super) fn pad_decimal(value: &str, places: usize) -> String {
if value.is_empty() {
return String::new();
}
if let Some(dot_pos) = value.find('.') {
let integer = &value[..dot_pos];
let frac = &value[dot_pos + 1..];
if frac.len() >= places {
format!("{integer}.{}", &frac[..places])
} else {
let mut padded = String::from(frac);
while padded.len() < places {
padded.push('0');
}
format!("{integer}.{padded}")
}
} else {
let zeros = "0".repeat(places);
format!("{value}.{zeros}")
}
}
pub(super) fn add_child_dec(arr: &mut Vec<String>, name: &str, value: Option<&str>, places: usize) {
if let Some(v) = value {
let formatted = pad_decimal(v, places);
arr.push(format!("<{name}>{}</{name}>", escape_xml(&formatted)));
}
}
pub(super) fn add_child_str_dec(arr: &mut Vec<String>, name: &str, value: &str, places: usize) {
let formatted = pad_decimal(value, places);
arr.push(format!("<{name}>{}</{name}>", escape_xml(&formatted)));
}
pub(super) fn validate_txt_lines(lines: &[&str], layout: &str) -> Vec<String> {
let mut errors = Vec::new();
let mut num = 0;
let mut entities: Option<HashMap<&str, &str>> = None;
for row in lines {
if row.is_empty() {
continue;
}
let fields: Vec<&str> = row.split('|').collect();
let ref_upper = fields[0].to_uppercase();
if ref_upper.is_empty() {
continue;
}
if ref_upper == "NOTAFISCAL" {
continue;
}
if ref_upper == "A" {
num = 0;
let ver = fields.get(1).unwrap_or(&"4.00");
entities = get_structure(ver, layout).ok();
}
if ref_upper == "I" {
num += 1;
}
let last_char = row.chars().last().unwrap_or(' ');
if last_char != '|' {
let char_desc = match last_char {
' ' => "[ESP]".to_string(),
'\r' => "[CR]".to_string(),
'\t' => "[TAB]".to_string(),
_ => String::new(),
};
errors.push(format!(
"ERRO: ({num}) Todas as linhas devem terminar com 'pipe' e n\u{e3}o {char_desc}. [{row}]"
));
continue;
}
let ent = match &entities {
Some(e) => e,
None => {
errors.push("ERRO: O TXT n\u{e3}o cont\u{e9}m um marcador A".into());
return errors;
}
};
if !ent.contains_key(ref_upper.as_str()) {
errors.push(format!(
"ERRO: ({num}) Essa refer\u{ea}ncia n\u{e3}o est\u{e1} definida. [{row}]"
));
continue;
}
let count = fields.len() - 1;
let def = ent[ref_upper.as_str()];
let default_count = def.split('|').count() - 1;
if default_count != count {
errors.push(format!(
"ERRO: ({num}) O n\u{fa}mero de par\u{e2}metros na linha est\u{e1} errado (esperado #{default_count}) -> (encontrado #{count}). [ {row} ] Esperado [ {def} ]"
));
continue;
}
for field in &fields {
if field.is_empty() {
continue;
}
if !field.trim().is_empty() && field.chars().all(|c| c == ' ') {
errors.push(format!(
"ERRO: ({num}) Existem apenas espa\u{e7}os no campo dos dados. [{row}]"
));
continue;
}
if field.contains('>')
|| field.contains('<')
|| field.contains('"')
|| field.contains('\'')
|| field.contains('\t')
|| field.contains('\r')
{
errors.push(format!(
"ERRO: ({num}) Existem caracteres especiais n\u{e3}o permitidos, como por ex. caracteres de controle, sinais de maior ou menor, aspas ou apostrofes, na entidade [{row}]"
));
continue;
}
}
}
errors
}