1use crate::{
6 capture::{Capture, PairListTarget, PolylineCategory, ProfileCurveCapture},
7 preflight::preflight_xml_tokens,
8 semantics::{positive_id, references, triple, units},
9 xml::{
10 attr, attributes, character_references, error, normalize_encoding, required, split_name,
11 Result,
12 },
13 LandXmlCancellation, LandXmlCapabilities, LandXmlCoordinateSystem,
14 LandXmlDiagnosticCode as Code, LandXmlExtension, LandXmlLimits, LandXmlPoint, LandXmlPolyline,
15 LandXmlRenderState, LandXmlSourceId, LandXmlSurface, LandXmlSurfaceKind, LandXmlTinDocument,
16};
17use quick_xml::{
18 events::{BytesStart, Event},
19 Reader,
20};
21mod capture;
22mod document;
23mod finalize;
24mod limits;
25mod path;
26mod profiles;
27pub(crate) mod state;
28mod text;
29mod version;
30
31pub(crate) use document::parse_landxml_document;
32pub(crate) use finalize::{require_units_for_renderable_tin, surface_draws_to_scale};
33pub(crate) use state::Parser;
34use state::{retained_properties, Frame, SurfaceBuilder};
35pub use version::*;
36
37pub fn parse_landxml_tin(input: &[u8]) -> Result<LandXmlTinDocument> {
39 parse_landxml_tin_with_cancel(input, &LandXmlLimits::default(), None)
40}
41
42pub fn parse_landxml_tin_with_cancel(
44 input: &[u8],
45 limits: &LandXmlLimits,
46 cancelled: Option<&dyn LandXmlCancellation>,
47) -> Result<LandXmlTinDocument> {
48 if input.len() > limits.max_bytes {
49 return Err(error(Code::InputTooLarge, "input exceeds byte limit"));
50 }
51 let assumed_units = crate::semantics::resolve_assumed_units(limits)?;
55 let input = normalize_encoding(input, limits, cancelled)?;
56 preflight_xml_tokens(&input, limits, cancelled)?;
57 let mut parser = Parser::new(limits, cancelled, assumed_units);
58 let mut reader = Reader::from_reader(input.as_slice());
59 reader.config_mut().trim_text(false);
60 let mut buffer = Vec::new();
61 loop {
62 parser.check_cancel_and_work(1)?;
63 let event = reader
64 .read_event_into(&mut buffer)
65 .map_err(|_| error(Code::InvalidXml, "malformed XML"))?;
66 if matches!(event, Event::Eof) {
67 break;
68 }
69 parser.consume_event(event)?;
70 buffer.clear();
71 }
72 if !parser.frames.is_empty() {
73 return Err(error(Code::InvalidXml, "unclosed XML element"));
74 }
75 parser.finish()
76}
77
78impl Parser<'_> {
79 pub(crate) fn consume_event(&mut self, event: Event<'_>) -> Result<()> {
82 match event {
83 Event::Start(start) => self.start(&start),
84 Event::Empty(start) => {
85 self.start(&start)?;
86 self.end(None)
87 }
88 Event::End(end) => self.end(Some(end.name().as_ref().as_bytes())),
89 Event::Text(text) => self.text(text.as_ref().as_bytes()),
90 Event::CData(text) => self.cdata(text.as_ref().as_bytes()),
91 Event::DocType(_) => Err(error(Code::DtdForbidden, "DOCTYPE is not allowed")),
92 _ => Ok(()),
93 }
94 }
95 fn start(&mut self, start: &BytesStart<'_>) -> Result<()> {
96 if self.frames.len() >= self.limits.max_depth {
97 return Err(error(Code::LimitExceeded, "XML depth limit exceeded"));
98 }
99 let name = start.name();
100 let (_, local, prefix) = split_name(name.as_ref().as_bytes(), self.limits.max_name_bytes)?;
101 let (attributes, namespaces, references) = attributes(start, &self.limits)?;
102 self.check_cancel_and_work(attributes.len())?;
103 self.check_character_references(references)?;
104 let mut inherited = self
105 .frames
106 .last()
107 .map_or_else(std::collections::HashMap::new, |frame| {
108 frame.namespaces.clone()
109 });
110 inherited.extend(namespaces);
111 let namespace = inherited.get(prefix).map(String::as_str);
112 if self.frames.is_empty() {
113 if self.root_seen {
114 return Err(error(
115 Code::InvalidXml,
116 "LandXML document has multiple root elements",
117 ));
118 }
119 self.root_seen = true;
120 if local != "LandXML" {
121 return Err(error(Code::InvalidSemantic, "root element is not LandXML"));
122 }
123 let capability = classify_landxml_version(namespace, attr(&attributes, "version"));
124 if !capability.supports_tin_ingestion() {
125 return Err(match capability {
126 crate::LandXmlVersionCapability::NotLandXml => error(
127 Code::UnsupportedNamespace,
128 "root namespace is not a recognized LandXML namespace",
129 ),
130 _ => error(
131 Code::UnsupportedVersion,
132 "LandXML namespaces require a known version declaration",
133 ),
134 });
135 }
136 self.schema = capability
137 .schema()
138 .expect("supported capability has a schema")
139 .to_owned();
140 self.target_namespace = namespace.map(str::to_owned);
141 if let Some(diagnostic) = compatibility_version_diagnostic(
142 capability,
143 attr(&attributes, "version").expect("supported capability has a version"),
144 ) {
145 self.record_capability_diagnostic(diagnostic)?;
146 }
147 self.version = required(&attributes, "version", "LandXML")?.to_owned();
148 }
149 let target = namespace == self.target_namespace.as_deref();
150 let sibling_ordinal = self.frames.last_mut().map_or(1, |parent| {
151 let ordinal = parent
152 .child_ordinals
153 .entry((target, local.to_owned()))
154 .or_insert(0);
155 *ordinal += 1;
156 *ordinal
157 });
158 let extension_root = !target
162 && !self.frames.is_empty()
163 && self.frames.last().is_some_and(|frame| frame.target);
164 if extension_root {
165 if self.extensions.len() >= self.limits.max_extensions {
166 return Err(error(
167 Code::LimitExceeded,
168 "extension record limit exceeded",
169 ));
170 }
171 self.extensions.push(LandXmlExtension {
172 namespace: namespace.unwrap_or("").to_owned(),
173 local_name: local.to_owned(),
174 path: self.path_with(local),
175 });
176 }
177 self.frames.push(Frame {
178 local: local.to_owned(),
179 target,
180 sibling_ordinal,
181 child_ordinals: std::collections::HashMap::new(),
182 overlay_name: matches!(local, "Boundary" | "Breakline" | "Contour")
183 .then(|| attr(&attributes, "name").map(str::to_owned))
184 .flatten(),
185 overlay_kind: matches!(local, "Boundary" | "Breakline" | "Contour")
186 .then(|| {
187 attr(&attributes, "bndType")
188 .or_else(|| attr(&attributes, "brkType"))
189 .or_else(|| attr(&attributes, "contType"))
190 .map(str::to_owned)
191 })
192 .flatten(),
193 overlay_properties: matches!(local, "Boundary" | "Breakline" | "Contour")
194 .then(|| retained_properties(&attributes))
195 .unwrap_or_default(),
196 namespaces: inherited,
197 });
198 if !target {
199 if local == "Corridor" {
200 self.record_preserved_only(
201 local,
202 crate::LandXmlPreservedOnlyExtensionKind::Corridor,
203 )?;
204 } else if local == "StringLine" {
205 self.record_preserved_only(
206 local,
207 crate::LandXmlPreservedOnlyExtensionKind::StringLine,
208 )?;
209 }
210 return Ok(());
211 }
212 match local {
213 "CoordinateSystem" if self.is_path(&["LandXML", "CoordinateSystem"]) => {
214 if self.coordinate_system.is_some() {
215 self.warnings.push(
216 "LandXML declares multiple root CoordinateSystem records; retained the last declaration"
217 .to_owned(),
218 );
219 }
220 self.coordinate_system = Some(LandXmlCoordinateSystem {
221 horizontal_datum: attr(&attributes, "horizontalDatum")
222 .map(|value| value.to_owned()),
223 vertical_datum: attr(&attributes, "verticalDatum")
224 .map(|value| value.to_owned()),
225 epsg_code: attr(&attributes, "epsgCode").map(|value| value.to_owned()),
226 name: attr(&attributes, "name").map(|value| value.to_owned()),
227 });
228 }
229 "Surface" if self.is_path(&["LandXML", "Surfaces", "Surface"]) => {
230 if self.surfaces_seen >= self.limits.max_surfaces {
231 return Err(error(Code::LimitExceeded, "surface limit exceeded"));
232 }
233 self.surfaces_seen += 1;
234 self.surface = Some(SurfaceBuilder {
235 name: required(&attributes, "name", "Surface")?.to_owned(),
236 kind: LandXmlSurfaceKind::Other,
237 points: Vec::new(),
238 canonical_vertices: Vec::new(),
239 source_data_points: Vec::new(),
240 ids: std::collections::HashSet::new(),
241 faces: Vec::new(),
242 face_visibility: Vec::new(),
243 hidden_face_count: 0,
244 boundaries: Vec::new(),
245 breaklines: Vec::new(),
246 contours: Vec::new(),
247 properties: retained_properties(&attributes),
248 definition_properties: crate::LandXmlProperties::new(),
249 })
250 }
251 "Definition" if self.is_path(&["LandXML", "Surfaces", "Surface", "Definition"]) => {
252 if let Some(surface) = &mut self.surface {
253 surface.definition_properties = retained_properties(&attributes);
254 surface.kind = match attr(&attributes, "surfType")
255 .map(str::to_ascii_uppercase)
256 .as_deref()
257 {
258 Some("TIN") => LandXmlSurfaceKind::Tin,
259 Some("GRID") => LandXmlSurfaceKind::Grid,
260 Some("VOLUME") => LandXmlSurfaceKind::Volume,
261 _ => LandXmlSurfaceKind::Other,
262 };
263 }
264 }
265 "Metric" | "Imperial" if self.is_path(&["LandXML", "Units", local]) => {
266 if self.units.is_some() {
267 return Err(error(
268 Code::InvalidSemantic,
269 "LandXML may declare units only once",
270 ));
271 }
272 self.units = Some(units(&attributes)?);
273 if self.assumed_units.is_some() {
279 self.warnings.push(
280 "an assumed linear unit override was supplied but LandXML/Units is \
281 declared; the declared units were used and the override was ignored"
282 .to_owned(),
283 );
284 }
285 }
286 "P" if self.is_path(&["LandXML", "Surfaces", "Surface", "Definition", "Pnts", "P"])
287 && self
288 .surface
289 .as_ref()
290 .is_some_and(|surface| surface.kind == LandXmlSurfaceKind::Tin) =>
291 {
292 self.capture = Some(Capture::Point {
293 id: positive_id(required(&attributes, "id", "point")?)?,
294 depth: self.frames.len(),
295 text: String::new(),
296 })
297 }
298 "F" if self.is_path(&[
299 "LandXML",
300 "Surfaces",
301 "Surface",
302 "Definition",
303 "Faces",
304 "F",
305 ]) && self
306 .surface
307 .as_ref()
308 .is_some_and(|surface| surface.kind == LandXmlSurfaceKind::Tin) =>
309 {
310 self.capture = Some(Capture::Face {
311 depth: self.frames.len(),
312 text: String::new(),
313 hidden: matches!(attr(&attributes, "i"), Some("1" | "true")),
314 })
315 }
316 "PntList3D" | "PntList2D" if self.source_data_point_dimension().is_some() => {
317 self.capture = Some(Capture::SourcePoints {
318 depth: self.frames.len(),
319 text: String::new(),
320 source_path: self.capture_path(),
321 coordinate_dimension: self
322 .source_data_point_dimension()
323 .expect("guarded above"),
324 })
325 }
326 "PntList3D" | "PntList2D" if self.overlay_category().is_some() => {
327 let overlay = self.frames.get(self.frames.len().saturating_sub(2));
328 self.capture = Some(Capture::Polyline {
329 depth: self.frames.len(),
330 text: String::new(),
331 category: self.overlay_category().expect("guarded above"),
332 name: overlay.and_then(|frame| frame.overlay_name.clone()),
333 kind: overlay.and_then(|frame| frame.overlay_kind.clone()),
334 properties: overlay.map_or_else(crate::LandXmlProperties::new, |frame| {
335 frame.overlay_properties.clone()
336 }),
337 source_path: self.capture_path(),
338 coordinate_dimension: if local == "PntList3D" { 3 } else { 2 },
339 })
340 }
341 _ => {}
342 }
343 self.start_road_semantics(local, &attributes)?;
344 Ok(())
345 }
346
347 fn end(&mut self, closing: Option<&[u8]>) -> Result<()> {
348 let frame = self
349 .frames
350 .last()
351 .cloned()
352 .ok_or_else(|| error(Code::InvalidXml, "unexpected closing element"))?;
353 if let Some(closing) = closing {
354 let (_, local, _) = split_name(closing, self.limits.max_name_bytes)?;
355 if local != frame.local {
356 return Err(error(Code::InvalidXml, "mismatched closing element"));
357 }
358 }
359 if self
360 .capture
361 .as_ref()
362 .is_some_and(|capture| capture.depth() == self.frames.len())
363 {
364 self.finish_capture()?;
365 }
366 if self.is_path(&["LandXML", "Surfaces", "Surface"]) {
367 self.finish_surface()?;
368 }
369 self.finish_road_element()?;
370 let closes_root = self.frames.len() == 1;
371 self.frames.pop();
372 if closes_root {
373 self.root_closed = true;
374 }
375 Ok(())
376 }
377
378 fn is_path(&self, expected: &[&str]) -> bool {
379 self.frames.len() == expected.len()
380 && self
381 .frames
382 .iter()
383 .zip(expected)
384 .all(|(frame, local)| frame.target && frame.local == *local)
385 }
386}