use std::collections::HashSet;
use std::path::{Path, PathBuf};
use roxmltree::{Document, Node};
use crate::ir::Ir;
use super::error::{Fault, ParseError, named_source};
use super::registry::TypeRegistry;
use super::schema::parse_schema;
use super::warn::{WarnState, set_source_name, source_name};
pub fn parse(xml: &str) -> Result<Ir, ParseError> {
let warn_state = WarnState::new();
set_source_name("<xml>".into());
parse_with_context(
xml,
None,
&mut HashSet::new(),
TypeRegistry::new(),
&warn_state,
)
}
#[allow(clippy::result_large_err)]
pub fn parse_with_shared(xml: &str, shared: &Ir) -> Result<Ir, ParseError> {
let warn_state = WarnState::new();
parse_with_context(
xml,
None,
&mut HashSet::new(),
TypeRegistry::from_parsed_schema(shared),
&warn_state,
)
}
#[allow(clippy::result_large_err)]
pub fn parse_with_xsd_validation(xml: &str) -> Result<Ir, ParseError> {
if let Err(e) = crate::xsd::validate_against_sbe_xsd(xml) {
return Err(ParseError::malformed_xml(
format!("XSD structural validation failed: {e}"),
xml,
));
}
parse(xml)
}
#[allow(clippy::result_large_err)]
pub fn parse_file(path: impl AsRef<Path>) -> Result<Ir, ParseError> {
let path = path.as_ref();
let warn_state = WarnState::new();
set_source_name(path.display().to_string());
let xml = std::fs::read_to_string(path).map_err(|e| {
ParseError::malformed_xml(format!("cannot read {}: {e}", path.display()), "")
})?;
let base_dir = path.parent();
let mut seen = HashSet::new();
if let Ok(canon) = path.canonicalize() {
seen.insert(canon);
}
parse_with_context(&xml, base_dir, &mut seen, TypeRegistry::new(), &warn_state)
}
#[allow(clippy::result_large_err)]
pub fn parse_file_with_shared(path: impl AsRef<Path>, shared: &Ir) -> Result<Ir, ParseError> {
let path = path.as_ref();
let warn_state = WarnState::new();
set_source_name(path.display().to_string());
let xml = std::fs::read_to_string(path).map_err(|e| {
ParseError::malformed_xml(format!("cannot read {}: {e}", path.display()), "")
})?;
let base_dir = path.parent();
let mut seen = HashSet::new();
if let Ok(canon) = path.canonicalize() {
seen.insert(canon);
}
parse_with_context(
&xml,
base_dir,
&mut seen,
TypeRegistry::from_parsed_schema(shared),
&warn_state,
)
}
pub(crate) fn parse_with_context(
xml: &str,
base_dir: Option<&Path>,
seen: &mut HashSet<PathBuf>,
initial_registry: TypeRegistry,
warn_state: &WarnState,
) -> Result<Ir, ParseError> {
let doc = match Document::parse(xml) {
Ok(d) => d,
Err(e) => return Err(ParseError::malformed_xml(e.to_string(), xml)),
};
let input = doc.input_text();
let root = doc
.root()
.children()
.find(Node::is_element)
.ok_or_else(|| Fault::missing_no_node("root <messageSchema> element"));
let root = match root {
Ok(n) => n,
Err(fault) => return Err(ParseError::from_fault(fault, input)),
};
if root.tag_name().name() != "messageSchema" {
return Err(ParseError::from_fault(
Fault::missing(root, "root <messageSchema> element"),
input,
));
}
let mut ir = parse_schema(root, base_dir, seen, initial_registry, warn_state)
.map_err(|fault| ParseError::from_fault(fault, input))?;
crate::resolve::resolve_schema(&mut ir, Some(input))?;
Ok(ir)
}