use std::collections::HashMap;
use std::fs;
use std::path::Path;
use std::time::Instant;
use quick_xml::reader::Reader;
use crate::protocol::{Dialect, Protocol};
use super::definition::XmlDialectDefinition;
use super::errors::{XmlInspectionError, XmlParseError};
use super::xml::XmlParser;
#[derive(Debug)]
pub struct XMLInspector {
sources: Vec<String>,
definitions: Vec<XmlDialectDefinition>,
}
impl XMLInspector {
pub fn new(sources: Vec<String>) -> Result<Self, XmlInspectionError> {
let definitions = Self::discover_definitions(sources.as_slice())?;
Ok(XMLInspector {
sources,
definitions,
})
}
pub fn src(&self) -> &[String] {
&self.sources
}
pub fn definitions(&self) -> &[XmlDialectDefinition] {
&self.definitions
}
pub fn discover_definitions(
paths: &[String],
) -> Result<Vec<XmlDialectDefinition>, XmlInspectionError> {
let mut dialects: Vec<XmlDialectDefinition> = Vec::new();
let mut dialect_ids: HashMap<String, String> = HashMap::new();
for path in paths {
let canonic = Path::new(&path).canonicalize()?;
let path = canonic.to_str().unwrap();
for entry in fs::read_dir(path)? {
let entry_path = entry?.path();
if entry_path.is_file()
&& entry_path
.extension()
.unwrap()
.to_str()
.unwrap()
.to_lowercase()
.eq("xml")
{
let path = entry_path.to_str().unwrap().to_string();
let definition = XmlDialectDefinition::new(&path);
#[allow(clippy::map_entry)]
if dialect_ids.contains_key(&definition.canonical_name()) {
return Err(XmlInspectionError::NamingCollision {
first: definition.name().to_string(),
second: dialect_ids
.get(&definition.canonical_name())
.unwrap()
.clone(),
canonical: definition.canonical_name(),
});
} else {
dialect_ids
.insert(definition.canonical_name(), definition.name().to_string());
}
dialects.push(definition);
}
}
}
Ok(dialects)
}
pub fn parse(&self) -> Result<Protocol, XmlParseError> {
let mut dialects: HashMap<String, Dialect> = HashMap::new();
log::info!("Parsing dialects.");
let started_at = Instant::now();
for def in &self.definitions {
if dialects.contains_key(&def.canonical_name()) {
continue;
}
Self::parse_definition(def, &mut dialects)?;
}
let ended_at = Instant::now();
let duration = ended_at - started_at;
log::info!("All dialects parsed.");
log::info!("Parsed dialects: {:?}", dialects.keys());
log::info!(
"Parse duration: {}s",
(duration.as_micros() as f64) / 1000000.0
);
Ok(Protocol::new(dialects))
}
pub fn parse_definition<'a>(
definition: &XmlDialectDefinition,
dialects: &'a mut HashMap<String, Dialect>,
) -> Result<&'a Dialect, XmlParseError> {
if dialects.contains_key(&definition.canonical_name()) {
return Ok(dialects.get(&definition.canonical_name()).unwrap());
}
for dependency in definition.includes() {
Self::parse_definition(dependency, dialects)?;
}
let mut enums = HashMap::new();
let mut messages = HashMap::new();
for dependency in definition.includes() {
for (name, enm) in dialects.get(&dependency.canonical_name()).unwrap().enums() {
enums.insert(name.clone(), enm.clone());
}
}
for dependency in definition.includes() {
for (&id, message) in dialects
.get(&dependency.canonical_name())
.unwrap()
.messages()
{
messages.insert(id, message.clone());
}
}
let started_at = Instant::now();
let mut parser: XmlParser = XmlParser::new(&mut enums, &mut messages);
let mut file_reader = Reader::from_file(definition.path()).unwrap();
parser.parse(definition.name(), &mut file_reader)?;
dialects.insert(
definition.canonical_name(),
Dialect::new(
definition.name().to_string(),
definition.version(),
definition.dialect(),
messages,
enums,
),
);
let ended_at = Instant::now();
let duration = ended_at - started_at;
if log::log_enabled!(log::Level::Debug) {
log::debug!("Parsed definition '{}'.", definition.name());
log::debug!("Definition path: {}", definition.path());
log::debug!("Definition version: {:?}", definition.version());
log::debug!("Definition dialect #: {:?}", definition.dialect());
log::debug!(
"Parse duration: {}s",
(duration.as_micros() as f64) / 1000000.0
);
}
Ok(dialects.get(&definition.canonical_name()).unwrap())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_dialect_paths() -> Vec<String> {
vec![
"./message_definitions/standard".to_string(),
"./message_definitions/extra".to_string(),
]
}
#[test]
fn dialects_are_available() {
let parser = XMLInspector::new(default_dialect_paths()).unwrap();
assert!(!parser.definitions().is_empty())
}
}