use crate::uslm::parser::ParseError;
use crate::uslm::{USLMElement, parser::parse};
use rayon::prelude::*;
use serde::{Serialize, de::DeserializeOwned};
use std::fs::{self, File};
use std::io::Read;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use time::Date;
type Result<T> = std::result::Result<T, ParseError>;
pub fn load_xml_file(path: &str) -> std::io::Result<String> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn month_from_number(n: i32) -> Result<time::Month> {
match n {
1 => Ok(time::Month::January),
2 => Ok(time::Month::February),
3 => Ok(time::Month::March),
4 => Ok(time::Month::April),
5 => Ok(time::Month::May),
6 => Ok(time::Month::June),
7 => Ok(time::Month::July),
8 => Ok(time::Month::August),
9 => Ok(time::Month::September),
10 => Ok(time::Month::October),
11 => Ok(time::Month::November),
12 => Ok(time::Month::December),
_ => Err(ParseError::InvalidDate),
}
}
pub fn date_str_to_date(date_str: &str) -> Result<Date> {
let date_split: Vec<&str> = date_str.split("-").collect();
if date_split.len() != 3 {
return Err(ParseError::InvalidDate);
}
let year_num = i32::from_str(date_split[0]).expect("year should be valid i32");
let month_num = i32::from_str(date_split[1]).expect("month should be valid i32");
let day_num = u8::from_str(date_split[2]).expect("day should be valid u8");
let month_enum = month_from_number(month_num).expect("month num shoudl be between 1-12");
let date = Date::from_calendar_date(year_num, month_enum, day_num);
match date {
Ok(d) => Ok(d),
Err(_e) => Err(ParseError::InvalidDate),
}
}
pub fn parse_uslm_xml(xml_path: &str, date: &str) -> Result<USLMElement> {
parse(xml_path, date)
}
pub fn write_json_file<T: Serialize>(data: &T, json_path: &str) -> Result<()> {
let json_string = serde_json::to_string_pretty(data)?;
let mut output = fs::File::create(json_path)?;
write!(output, "{}", json_string)?;
Ok(())
}
pub fn read_json_file<T: DeserializeOwned>(json_path: &str) -> Result<T> {
let json_string = fs::read_to_string(json_path)?;
let data = serde_json::from_str(&json_string)?;
Ok(data)
}
pub fn parse_uslm_to_json(xml_path: &str, date: &str, json_path: &str) -> Result<()> {
let element = parse_uslm_xml(xml_path, date)?;
write_json_file(&element, json_path)?;
Ok(())
}
pub fn parse_uslm_directory(input_dir: &str, date: &str) -> Result<Vec<USLMElement>> {
let dir_path = Path::new(input_dir);
let xml_files: Vec<PathBuf> = fs::read_dir(dir_path)
.map_err(|e| ParseError::Io(e))?
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.extension()? == "xml" {
Some(path)
} else {
None
}
})
.collect();
let results: Vec<Result<USLMElement>> = xml_files
.par_iter()
.map(|path| {
let path_str = path.to_str().ok_or_else(|| {
ParseError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid path encoding",
))
})?;
parse_uslm_xml(path_str, date)
})
.collect();
let mut elements = Vec::new();
let mut errors = Vec::new();
for result in results {
match result {
Ok(element) => elements.push(element),
Err(e) => errors.push(e),
}
}
if !errors.is_empty() {
let error_msg = format!("Failed to parse {} files", errors.len());
return Err(ParseError::UnableToParseElement(error_msg));
}
Ok(elements)
}
pub fn parse_uslm_directory_to_json(input_dir: &str, date: &str, output_dir: &str) -> Result<()> {
let dir_path = Path::new(input_dir);
let out_path = Path::new(output_dir);
fs::create_dir_all(out_path)?;
let xml_files: Vec<PathBuf> = fs::read_dir(dir_path)
.map_err(|e| ParseError::Io(e))?
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.extension()? == "xml" {
Some(path)
} else {
None
}
})
.collect();
let results: Vec<Result<()>> = xml_files
.par_iter()
.map(|path| {
let path_str = path.to_str().ok_or_else(|| {
ParseError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid path encoding",
))
})?;
let file_stem = path.file_stem().and_then(|s| s.to_str()).ok_or_else(|| {
ParseError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid filename",
))
})?;
let output_path = out_path.join(format!("{}.json", file_stem));
let output_str = output_path.to_str().ok_or_else(|| {
ParseError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid output path encoding",
))
})?;
parse_uslm_to_json(path_str, date, output_str)
})
.collect();
let errors: Vec<ParseError> = results.into_iter().filter_map(|r| r.err()).collect();
if !errors.is_empty() {
let error_msg = format!("Failed to process {} files", errors.len());
return Err(ParseError::UnableToParseElement(error_msg));
}
Ok(())
}