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::{IncludeWalk, parse_schema};
use super::warn::WarnState;
pub fn parse(xml: &str) -> Result<Ir, ParseError> {
let warn_state = WarnState::new("<xml>".into());
parse_with_context(
xml,
None,
&mut IncludeWalk::new(),
TypeRegistry::new(),
&warn_state,
&mut Vec::new(),
)
}
#[allow(clippy::result_large_err)]
pub fn parse_with_shared(xml: &str, shared: &Ir) -> Result<Ir, ParseError> {
let warn_state = WarnState::new("<xml>".into());
parse_with_context(
xml,
None,
&mut IncludeWalk::new(),
TypeRegistry::from_parsed_schema(shared),
&warn_state,
&mut Vec::new(),
)
}
#[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(match &e {
crate::xsd::XsdValidationError::MalformedXml(_) => {
ParseError::malformed_xml("<xml>", e.to_string(), xml)
}
_ => ParseError::Invalid {
what: "SBE schema".into(),
value: e.to_string(),
source_code: named_source("<xml>", xml),
span: None,
},
});
}
parse(xml)
}
pub(crate) struct ParsedFile {
pub ir: Ir,
pub dependencies: Vec<PathBuf>,
}
#[allow(clippy::result_large_err)]
pub fn parse_file(path: impl AsRef<Path>) -> Result<Ir, ParseError> {
Ok(parse_file_with_deps(path)?.ir)
}
#[allow(clippy::result_large_err)]
pub(crate) fn parse_file_with_deps(path: impl AsRef<Path>) -> Result<ParsedFile, ParseError> {
parse_path_with_registry(path.as_ref(), TypeRegistry::new())
}
#[allow(clippy::result_large_err)]
pub fn parse_file_with_shared(path: impl AsRef<Path>, shared: &Ir) -> Result<Ir, ParseError> {
Ok(parse_file_with_shared_deps(path, shared)?.ir)
}
#[allow(clippy::result_large_err)]
pub(crate) fn parse_file_with_shared_deps(
path: impl AsRef<Path>,
shared: &Ir,
) -> Result<ParsedFile, ParseError> {
parse_path_with_registry(path.as_ref(), TypeRegistry::from_parsed_schema(shared))
}
#[allow(clippy::result_large_err)]
fn parse_path_with_registry(
path: &Path,
initial_registry: TypeRegistry,
) -> Result<ParsedFile, ParseError> {
let name = path.display().to_string();
let warn_state = WarnState::new(name.clone());
let xml = std::fs::read_to_string(path).map_err(|e| ParseError::io(path, e))?;
let base_dir = path.parent();
let mut dependencies = Vec::new();
let root = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
dependencies.push(root.clone());
let mut walk = IncludeWalk::with_root(root);
let ir = parse_with_context(
&xml,
base_dir,
&mut walk,
initial_registry,
&warn_state,
&mut dependencies,
)?;
Ok(ParsedFile {
ir,
dependencies: stabilize_dependencies(path, dependencies),
})
}
fn stabilize_dependencies(root: &Path, dependencies: Vec<PathBuf>) -> Vec<PathBuf> {
let root_key = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
let mut seen = HashSet::new();
let mut out = Vec::new();
seen.insert(root_key.clone());
out.push(root_key);
let mut rest = dependencies;
rest.sort();
for path in rest {
let key = path.canonicalize().unwrap_or(path);
if seen.insert(key.clone()) {
out.push(key);
}
}
out
}
pub(crate) fn parse_with_context(
xml: &str,
base_dir: Option<&Path>,
walk: &mut IncludeWalk,
initial_registry: TypeRegistry,
warn_state: &WarnState,
dependencies: &mut Vec<PathBuf>,
) -> Result<Ir, ParseError> {
let doc = match Document::parse(xml) {
Ok(d) => d,
Err(e) => {
return Err(ParseError::malformed_xml(
&warn_state.name,
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(&warn_state.name, fault, input)),
};
if root.tag_name().name() != "messageSchema" {
return Err(ParseError::from_fault(
&warn_state.name,
Fault::missing(root, "root <messageSchema> element"),
input,
));
}
let mut ir = parse_schema(
root,
base_dir,
walk,
initial_registry,
warn_state,
dependencies,
)
.map_err(|fault| ParseError::from_fault(&warn_state.name, fault, input))?;
crate::resolve::resolve_schema(&mut ir, Some(input))?;
Ok(ir)
}