1use quick_xml::{events::Event, Reader};
8
9use super::{
10 model::LandXmlPlanReferenceIndex, LandXmlCgPoint, LandXmlMonument, LandXmlParcel,
11 LandXmlPlanDocument, LandXmlPlanFeature,
12};
13use crate::{
14 classify_landxml_version, compatibility_version_diagnostic,
15 preflight::preflight_xml_tokens,
16 semantics::units,
17 xml::{
18 attr, attributes, character_references, error, normalize_encoding, split_name, unescape,
19 Attributes, Result,
20 },
21 LandXmlCancellation, LandXmlCapabilityDiagnostic, LandXmlDiagnosticCode as Code, LandXmlLimits,
22 LandXmlSourceId, LandXmlUnits, LandXmlVersionCapability,
23};
24
25mod actions;
26mod driver;
27mod state;
28mod value;
29use state::{Active, Capture, Frame, GeometryBuilder};
30pub(super) use value::area_scale;
31#[derive(Clone, Debug)]
33pub struct LandXmlPlanLimits {
34 pub xml: LandXmlLimits,
35 pub max_cogo_points: usize,
36 pub max_monuments: usize,
37 pub max_plan_features: usize,
38 pub max_parcels: usize,
39 pub max_geometry: usize,
40 pub max_vertices: usize,
41}
42impl Default for LandXmlPlanLimits {
43 fn default() -> Self {
44 Self {
45 xml: LandXmlLimits::default(),
46 max_cogo_points: 5_000_000,
47 max_monuments: 1_000_000,
48 max_plan_features: 1_000_000,
49 max_parcels: 1_000_000,
50 max_geometry: 5_000_000,
51 max_vertices: 10_000_000,
52 }
53 }
54}
55
56pub fn parse_landxml_plan(input: &[u8]) -> Result<LandXmlPlanDocument> {
58 parse_landxml_plan_with_cancel(input, &LandXmlPlanLimits::default(), None)
59}
60pub fn parse_landxml_plan_with_cancel(
62 input: &[u8],
63 limits: &LandXmlPlanLimits,
64 cancelled: Option<&dyn LandXmlCancellation>,
65) -> Result<LandXmlPlanDocument> {
66 if input.len() > limits.xml.max_bytes {
67 return Err(error(Code::InputTooLarge, "input exceeds byte limit"));
68 }
69 let input = normalize_encoding(input, &limits.xml, cancelled)?;
70 preflight_xml_tokens(&input, &limits.xml, cancelled)?;
71 let mut parser = Parser::new(limits, cancelled);
72 let mut reader = Reader::from_reader(input.as_slice());
73 reader.config_mut().trim_text(false);
74 let mut buffer = Vec::new();
75 loop {
76 let event = reader
77 .read_event_into(&mut buffer)
78 .map_err(|_| error(Code::InvalidXml, "malformed XML"))?;
79 if matches!(event, Event::Eof) {
80 break;
81 }
82 parser.consume_event(event)?;
83 buffer.clear();
84 }
85 if !parser.root_seen {
86 return Err(error(Code::InvalidXml, "LandXML document is empty"));
87 }
88 if !parser.frames.is_empty() {
89 return Err(error(Code::InvalidXml, "unclosed XML element"));
90 }
91 Ok(parser.document())
92}
93
94#[allow(private_interfaces)]
97pub(crate) struct Parser<'a> {
98 pub(super) limits: LandXmlPlanLimits,
99 pub(super) cancelled: Option<&'a dyn LandXmlCancellation>,
100 pub(super) work: usize,
101 pub(super) characters: usize,
102 pub(super) references: usize,
103 pub(super) frames: Vec<Frame>,
104 pub(super) units: Option<LandXmlUnits>,
105 pub(super) area_unit: Option<String>,
106 pub(super) area_scale_to_square_meters: Option<f64>,
107 pub(super) schema: String,
108 pub(super) version: String,
109 pub(super) capability_diagnostics: Vec<LandXmlCapabilityDiagnostic>,
110 pub(super) target_namespace: Option<String>,
111 pub(super) root_seen: bool,
112 pub(super) cogo_points: Vec<LandXmlCgPoint>,
113 pub(super) reference_index: LandXmlPlanReferenceIndex,
114 pub(super) monuments: Vec<LandXmlMonument>,
115 pub(super) features: Vec<LandXmlPlanFeature>,
116 pub(super) parcels: Vec<LandXmlParcel>,
117 pub(super) active: Vec<Active>,
118 pub(super) active_depths: Vec<usize>,
119 pub(super) geometry: Option<GeometryBuilder>,
120 pub(super) capture: Option<Capture>,
121 pub(super) scope_stack: Vec<(usize, LandXmlSourceId)>,
122 pub(super) reference_scope: Option<LandXmlSourceId>,
123 pub(super) scope_ordinal: usize,
124 pub(super) cogo_ordinal: usize,
125 pub(super) monument_ordinal: usize,
126 pub(super) vertices: usize,
127 pub(super) geometry_count: usize,
128 pub(super) feature_ordinal: usize,
129 pub(super) parcel_ordinal: usize,
130}
131impl<'a> Parser<'a> {
132 fn new(limits: &LandXmlPlanLimits, cancelled: Option<&'a dyn LandXmlCancellation>) -> Self {
133 Self {
134 limits: limits.clone(),
135 cancelled,
136 work: 0,
137 characters: 0,
138 references: 0,
139 frames: Vec::new(),
140 units: None,
141 area_unit: None,
142 area_scale_to_square_meters: None,
143 schema: String::new(),
144 version: String::new(),
145 capability_diagnostics: Vec::new(),
146 target_namespace: None,
147 root_seen: false,
148 cogo_points: Vec::new(),
149 reference_index: LandXmlPlanReferenceIndex::default(),
150 monuments: Vec::new(),
151 features: Vec::new(),
152 parcels: Vec::new(),
153 active: Vec::new(),
154 active_depths: Vec::new(),
155 geometry: None,
156 capture: None,
157 scope_stack: Vec::new(),
158 reference_scope: None,
159 scope_ordinal: 0,
160 cogo_ordinal: 0,
161 monument_ordinal: 0,
162 vertices: 0,
163 geometry_count: 0,
164 feature_ordinal: 0,
165 parcel_ordinal: 0,
166 }
167 }
168 pub(super) fn check(&mut self, added: usize) -> Result<()> {
169 if self
170 .cancelled
171 .is_some_and(LandXmlCancellation::is_cancelled)
172 {
173 return Err(error(Code::Cancelled, "ingestion cancelled"));
174 }
175 self.work = self
176 .work
177 .checked_add(added)
178 .ok_or_else(|| error(Code::LimitExceeded, "work limit exceeded"))?;
179 if self.work > self.limits.xml.max_work {
180 return Err(error(Code::LimitExceeded, "work limit exceeded"));
181 }
182 Ok(())
183 }
184
185 pub(crate) fn consume_event(&mut self, event: Event<'_>) -> Result<()> {
186 self.check(1)?;
187 match event {
188 Event::Start(value) => self.start(&value),
189 Event::Empty(value) => {
190 self.start(&value)?;
191 self.end(None)
192 }
193 Event::End(value) => self.end(Some(value.name().as_ref().as_bytes())),
194 Event::Text(value) => self.text(value.as_ref().as_bytes()),
195 Event::CData(value) => self.cdata(value.as_ref().as_bytes()),
196 Event::DocType(_) => Err(error(Code::DtdForbidden, "DOCTYPE is not allowed")),
197 _ => Ok(()),
198 }
199 }
200
201 pub(crate) fn has_open_frames(&self) -> bool {
202 !self.frames.is_empty()
203 }
204}
205
206impl Parser<'static> {
207 pub(crate) fn new_stream(limits: LandXmlPlanLimits) -> Self {
208 Self::new(&limits, None)
209 }
210}