1use std::collections::HashSet;
4use std::path::{Path, PathBuf};
5
6use roxmltree::{Document, Node};
7
8use crate::ir::Ir;
9
10use super::error::{Fault, ParseError, named_source};
11use super::registry::TypeRegistry;
12use super::schema::{IncludeWalk, parse_schema};
13use super::warn::WarnState;
14
15pub fn parse(xml: &str) -> Result<Ir, ParseError> {
22 let warn_state = WarnState::new("<xml>".into());
23 parse_with_context(
24 xml,
25 None,
26 &mut IncludeWalk::new(),
27 TypeRegistry::new(),
28 &warn_state,
29 &mut Vec::new(),
30 )
31}
32
33#[allow(clippy::result_large_err)]
46pub fn parse_with_shared(xml: &str, shared: &Ir) -> Result<Ir, ParseError> {
47 let warn_state = WarnState::new("<xml>".into());
48 parse_with_context(
49 xml,
50 None,
51 &mut IncludeWalk::new(),
52 TypeRegistry::from_parsed_schema(shared),
53 &warn_state,
54 &mut Vec::new(),
55 )
56}
57
58#[allow(clippy::result_large_err)]
69pub fn parse_with_xsd_validation(xml: &str) -> Result<Ir, ParseError> {
70 if let Err(e) = crate::xsd::validate_against_sbe_xsd(xml) {
71 return Err(match &e {
72 crate::xsd::XsdValidationError::MalformedXml(_) => {
73 ParseError::malformed_xml("<xml>", e.to_string(), xml)
74 }
75 _ => ParseError::Invalid {
76 what: "SBE schema".into(),
77 value: e.to_string(),
78 source_code: named_source("<xml>", xml),
79 span: None,
80 },
81 });
82 }
83 parse(xml)
84}
85
86pub(crate) struct ParsedFile {
91 pub ir: Ir,
92 pub dependencies: Vec<PathBuf>,
94}
95
96#[allow(clippy::result_large_err)]
102pub fn parse_file(path: impl AsRef<Path>) -> Result<Ir, ParseError> {
103 Ok(parse_file_with_deps(path)?.ir)
104}
105
106#[allow(clippy::result_large_err)]
108pub(crate) fn parse_file_with_deps(path: impl AsRef<Path>) -> Result<ParsedFile, ParseError> {
109 parse_path_with_registry(path.as_ref(), TypeRegistry::new())
110}
111
112#[allow(clippy::result_large_err)]
119pub fn parse_file_with_shared(path: impl AsRef<Path>, shared: &Ir) -> Result<Ir, ParseError> {
120 Ok(parse_file_with_shared_deps(path, shared)?.ir)
121}
122
123#[allow(clippy::result_large_err)]
125pub(crate) fn parse_file_with_shared_deps(
126 path: impl AsRef<Path>,
127 shared: &Ir,
128) -> Result<ParsedFile, ParseError> {
129 parse_path_with_registry(path.as_ref(), TypeRegistry::from_parsed_schema(shared))
130}
131
132#[allow(clippy::result_large_err)]
133fn parse_path_with_registry(
134 path: &Path,
135 initial_registry: TypeRegistry,
136) -> Result<ParsedFile, ParseError> {
137 let name = path.display().to_string();
138 let warn_state = WarnState::new(name.clone());
139 let xml = std::fs::read_to_string(path).map_err(|e| ParseError::io(path, e))?;
140 let base_dir = path.parent();
141 let mut dependencies = Vec::new();
142 let root = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
145 dependencies.push(root.clone());
146 let mut walk = IncludeWalk::with_root(root);
147 let ir = parse_with_context(
148 &xml,
149 base_dir,
150 &mut walk,
151 initial_registry,
152 &warn_state,
153 &mut dependencies,
154 )?;
155 Ok(ParsedFile {
156 ir,
157 dependencies: stabilize_dependencies(path, dependencies),
158 })
159}
160
161fn stabilize_dependencies(root: &Path, dependencies: Vec<PathBuf>) -> Vec<PathBuf> {
164 let root_key = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
165 let mut seen = HashSet::new();
166 let mut out = Vec::new();
167 seen.insert(root_key.clone());
168 out.push(root_key);
169 let mut rest = dependencies;
170 rest.sort();
171 for path in rest {
172 let key = path.canonicalize().unwrap_or(path);
173 if seen.insert(key.clone()) {
174 out.push(key);
175 }
176 }
177 out
178}
179
180pub(crate) fn parse_with_context(
183 xml: &str,
184 base_dir: Option<&Path>,
185 walk: &mut IncludeWalk,
186 initial_registry: TypeRegistry,
187 warn_state: &WarnState,
188 dependencies: &mut Vec<PathBuf>,
189) -> Result<Ir, ParseError> {
190 let doc = match Document::parse(xml) {
191 Ok(d) => d,
192 Err(e) => {
193 return Err(ParseError::malformed_xml(
194 &warn_state.name,
195 e.to_string(),
196 xml,
197 ));
198 }
199 };
200 let input = doc.input_text();
201 let root = doc
202 .root()
203 .children()
204 .find(Node::is_element)
205 .ok_or_else(|| Fault::missing_no_node("root <messageSchema> element"));
206 let root = match root {
207 Ok(n) => n,
208 Err(fault) => return Err(ParseError::from_fault(&warn_state.name, fault, input)),
209 };
210 if root.tag_name().name() != "messageSchema" {
211 return Err(ParseError::from_fault(
212 &warn_state.name,
213 Fault::missing(root, "root <messageSchema> element"),
214 input,
215 ));
216 }
217 let mut ir = parse_schema(
218 root,
219 base_dir,
220 walk,
221 initial_registry,
222 warn_state,
223 dependencies,
224 )
225 .map_err(|fault| ParseError::from_fault(&warn_state.name, fault, input))?;
226 crate::resolve::resolve_schema(&mut ir, Some(input))?;
227 Ok(ir)
228}