1use std::path::Path;
2
3use quick_xml::events::{BytesStart, Event};
4use quick_xml::Reader;
5
6use crate::base::{intern, AttrDef, ClassDef, Element, FastMap, FastSet, FieldValue, TypeRegistry};
7use crate::registry;
8
9pub struct CimDataset {
10 pub entries: FastMap<String, Element>,
11 pub by_type: FastMap<String, Vec<String>>,
13}
14
15impl Default for CimDataset {
16 fn default() -> Self {
17 Self::new()
18 }
19}
20
21impl CimDataset {
22 pub fn new() -> Self {
23 Self {
24 entries: FastMap::default(),
25 by_type: FastMap::default(),
26 }
27 }
28
29 pub fn decode_str(content: &str) -> Result<Self, Box<dyn std::error::Error>> {
31 let mut ds = Self::new();
32 parse_rdf(content, registry::type_registry(), &mut ds)?;
33 Ok(ds)
34 }
35
36 pub fn decode_file(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
38 Self::decode_str(&std::fs::read_to_string(path)?)
39 }
40
41 pub fn decode_files(paths: &[&Path]) -> Result<Self, Box<dyn std::error::Error>> {
43 let mut combined = Self::new();
44 for path in paths {
45 let file_ds = Self::decode_file(path)?;
46 combined.merge(file_ds);
47 }
48 Ok(combined)
49 }
50
51 pub fn decode_files_parallel_separate(paths: &[&Path]) -> Result<Vec<Self>, Box<dyn std::error::Error>> {
55 if paths.len() <= 1 {
56 return paths.iter().map(|p| Self::decode_file(p)).collect();
57 }
58 let results: Vec<Result<Self, String>> = std::thread::scope(|s| {
59 paths
60 .iter()
61 .map(|p| s.spawn(|| Self::decode_file(p).map_err(|e| e.to_string())))
62 .collect::<Vec<_>>()
63 .into_iter()
64 .map(|h| h.join().expect("decode thread panicked"))
65 .collect()
66 });
67 results
68 .into_iter()
69 .collect::<Result<_, String>>()
70 .map_err(|e| e.into())
71 }
72
73 pub fn decode_files_parallel(paths: &[&Path]) -> Result<Self, Box<dyn std::error::Error>> {
75 let datasets = Self::decode_files_parallel_separate(paths)?;
76 Ok(datasets
77 .into_iter()
78 .reduce(|mut a, b| {
79 a.merge(b);
80 a
81 })
82 .unwrap_or_default())
83 }
84
85 pub fn merge(&mut self, other: CimDataset) {
95 let reg = registry::type_registry();
96 let mut moved: FastMap<&'static str, FastSet<String>> = FastMap::default();
97 for (mrid, incoming) in other.entries {
98 if let Some(existing) = self.entries.get_mut(&mrid) {
99 existing.merge_from(&incoming);
100 let old = existing.class();
101 let mut changed = false;
102 for t in incoming.types() {
103 changed |= existing.add_type(t, reg);
104 }
105 if changed {
106 moved.entry(old.qualified).or_default().insert(mrid.clone());
107 self.by_type.entry(existing.type_name().to_string()).or_default().push(mrid);
108 }
109 } else {
110 let type_name = incoming.type_name().to_string();
111 self.by_type.entry(type_name).or_default().push(mrid.clone());
112 self.entries.insert(mrid, incoming);
113 }
114 }
115 for (old, mrids) in moved {
117 if let Some(bucket) = self.by_type.get_mut(old) {
118 bucket.retain(|m| !mrids.contains(m));
119 }
120 }
121 }
122
123 pub fn set(&mut self, mrid: String, element: Element) {
126 let new_type = element.type_name().to_string();
127 if let Some(old) = self.entries.get(&mrid) {
128 let old_type = old.type_name();
129 if old_type != new_type
130 && let Some(v) = self.by_type.get_mut(old_type) {
131 v.retain(|m| m != &mrid);
132 }
133 }
134 let already_indexed = self
135 .by_type
136 .get(&new_type)
137 .is_some_and(|v| v.contains(&mrid));
138 if !already_indexed {
139 self.by_type.entry(new_type).or_default().push(mrid.clone());
140 }
141 self.entries.insert(mrid, element);
142 }
143
144 pub fn remove(&mut self, mrid: &str) -> Option<Element> {
147 let removed = self.entries.remove(mrid)?;
148 if let Some(v) = self.by_type.get_mut(removed.type_name()) {
149 v.retain(|m| m != mrid);
150 }
151 Some(removed)
152 }
153}
154
155fn index(by_type: &mut FastMap<String, Vec<String>>, type_name: &'static str, mrid: String) {
160 if let Some(bucket) = by_type.get_mut(type_name) {
161 bucket.push(mrid);
162 } else {
163 by_type.insert(type_name.to_string(), vec![mrid]);
164 }
165}
166
167type Table = FastMap<&'static str, &'static ClassDef>;
168
169#[derive(Default)]
175struct Scope {
176 prefixes: Vec<(Vec<u8>, Option<&'static Table>)>,
177 default: Option<&'static Table>,
178}
179
180impl Scope {
181 fn absorb(&mut self, e: &BytesStart, reg: &'static TypeRegistry) {
183 for attr in e.attributes().flatten() {
184 let key = attr.key.as_ref();
185 let Some(rest) = key.strip_prefix(b"xmlns".as_slice()) else {
186 continue;
187 };
188 let Ok(ns) = std::str::from_utf8(&attr.value) else {
189 continue;
190 };
191 let table = reg.ns_table(ns).or_else(|| {
193 if ns.ends_with('#') || ns.ends_with('/') {
194 None
195 } else {
196 reg.ns_table(&format!("{ns}#"))
197 }
198 });
199 match rest.strip_prefix(b":".as_slice()) {
200 Some(prefix) => {
201 let prefix = prefix.to_vec();
202 match self.prefixes.iter_mut().find(|(k, _)| *k == prefix) {
203 Some(slot) => slot.1 = table,
204 None => self.prefixes.push((prefix, table)),
205 }
206 }
207 None if rest.is_empty() => self.default = table,
208 None => {}
209 }
210 }
211 }
212
213 fn table(&self, prefix: Option<&[u8]>) -> Option<&'static Table> {
214 match prefix {
215 None => self.default,
216 Some(p) => self
217 .prefixes
218 .iter()
219 .find(|(k, _)| k.as_slice() == p)
220 .and_then(|(_, t)| *t),
221 }
222 }
223
224}
225
226fn scan_attrs(e: &BytesStart) -> Result<(String, bool), Box<dyn std::error::Error>> {
231 let mut mrid = String::new();
232 let mut has_xmlns = false;
233 for attr in e.attributes().flatten() {
234 let key = attr.key.as_ref();
235 if key == b"rdf:about" || key == b"rdf:ID" {
236 mrid = strip_fragment(std::str::from_utf8(&attr.value)?);
237 } else if key.starts_with(b"xmlns") {
238 has_xmlns = true;
239 }
240 }
241 Ok((mrid, has_xmlns))
242}
243
244fn split_qname(raw: &[u8]) -> (Option<&[u8]>, &[u8]) {
246 match raw.iter().position(|b| *b == b':') {
247 Some(i) => (Some(&raw[..i]), &raw[i + 1..]),
248 None => (None, raw),
249 }
250}
251
252fn resolve(
262 scope: &Scope,
263 reg: &'static TypeRegistry,
264 raw: &[u8],
265) -> Result<Option<&'static ClassDef>, Box<dyn std::error::Error>> {
266 let (prefix, local_bytes) = split_qname(raw);
267 let local = std::str::from_utf8(local_bytes)?;
268 if let Some(class) = scope.table(prefix).and_then(|t| t.get(local)) {
269 return Ok(Some(*class));
270 }
271 Ok(reg.bare().get(local).copied())
272}
273
274fn parse_rdf(
276 content: &str,
277 reg: &'static TypeRegistry,
278 ds: &mut CimDataset,
279) -> Result<(), Box<dyn std::error::Error>> {
280 let mut reader = Reader::from_str(content);
281 let mut buf = Vec::new();
282
283 let mut depth: u32 = 0;
284 let mut scope = Scope::default();
285 let mut nested: Option<Scope> = None;
287 let mut current: Option<Element> = None;
290 let mut declared: Option<&'static FastMap<&'static str, &'static AttrDef>> = None;
292 let mut pending_key: Option<&'static str> = None;
293 let mut pending_lang: Option<String> = None;
295 let key_of = |declared: Option<&'static FastMap<&'static str, &'static AttrDef>>, local: &str| {
298 declared.and_then(|d| d.get(local)).map_or_else(|| intern(local), |a| a.id)
299 };
300
301 loop {
302 match reader.read_event_into(&mut buf) {
303 Ok(Event::Start(ref e)) => {
304 depth += 1;
305
306 match depth {
307 1 => scope.absorb(e, reg),
308 2 => {
309 let (mrid, has_xmlns) = scan_attrs(e)?;
314 if has_xmlns {
315 let mut inner = Scope {
316 prefixes: scope.prefixes.clone(),
317 default: scope.default,
318 };
319 inner.absorb(e, reg);
320 nested = Some(std::mem::replace(&mut scope, inner));
321 }
322
323 current = resolve(&scope, reg, e.name().as_ref())?.map(|class| Element::new(class, mrid));
324 declared = current.as_ref().and_then(|c| reg.declared(c.class()));
325 }
326 3 => {
327 if let Some(element) = current.as_mut() {
328 let name = e.name();
329 let (_, local_bytes) = split_qname(name.as_ref());
330 let local = key_of(declared, std::str::from_utf8(local_bytes)?);
331 let (res, lang) = field_attrs(e.attributes())?;
332 if let Some(res) = res {
333 element.add_field(local, FieldValue::Resource(res));
334 } else {
335 pending_key = Some(local);
336 pending_lang = lang;
337 }
338 }
339 }
340 _ => {}
341 }
342 }
343
344 Ok(Event::Empty(ref e)) => {
345 match depth {
346 1 => {
347 let (mrid, has_xmlns) = scan_attrs(e)?;
352 let local_scope = if has_xmlns {
353 let mut inner = Scope {
354 prefixes: scope.prefixes.clone(),
355 default: scope.default,
356 };
357 inner.absorb(e, reg);
358 Some(inner)
359 } else {
360 None
361 };
362 let active = local_scope.as_ref().unwrap_or(&scope);
363 if !mrid.is_empty()
364 && let Some(class) = resolve(active, reg, e.name().as_ref())? {
365 index(&mut ds.by_type, class.qualified, mrid.clone());
366 ds.entries.insert(mrid.clone(), Element::new(class, mrid));
367 }
368 }
369 2 => {
370 if let Some(element) = current.as_mut()
372 && let Some(res) = find_resource(e.attributes())? {
373 let name = e.name();
374 let (_, local_bytes) = split_qname(name.as_ref());
375 let local = key_of(declared, std::str::from_utf8(local_bytes)?);
376 element.add_field(local, FieldValue::Resource(res));
377 }
378 }
379 _ => {}
380 }
381 }
382
383 Ok(Event::Text(ref e)) => {
384 if depth == 3
385 && let (Some(element), Some(key)) = (current.as_mut(), pending_key.take()) {
386 let text = e.unescape()?.trim().to_string();
387 if !text.is_empty() {
388 element.add_field(key, FieldValue::Text(text));
389 if let Some(lang) = pending_lang.take() {
390 element.add_lang(key, &lang);
391 }
392 }
393 }
394 }
395
396 Ok(Event::End(_)) => {
397 if depth == 2 {
398 pending_key = None;
399 pending_lang = None;
400 if let Some(element) = current.take()
401 && !element.mrid().is_empty() {
402 index(&mut ds.by_type, element.type_name(), element.mrid().to_string());
403 ds.entries.insert(element.mrid().to_string(), element);
404 }
405 if let Some(outer) = nested.take() {
406 scope = outer;
407 }
408 }
409 depth = depth.saturating_sub(1);
410 }
411
412 Ok(Event::Eof) => break,
413 Err(e) => return Err(Box::new(e)),
414 _ => {}
415 }
416 buf.clear();
417 }
418
419 Ok(())
420}
421
422fn strip_fragment(s: &str) -> String {
427 if let Some(i) = s.rfind('#') {
428 s[i + 1..].to_string()
429 } else {
430 s.trim_start_matches('#').to_string()
431 }
432}
433
434
435fn field_attrs(
438 attrs: quick_xml::events::attributes::Attributes<'_>,
439) -> Result<(Option<String>, Option<String>), Box<dyn std::error::Error>> {
440 let (mut res, mut lang) = (None, None);
441 for attr in attrs.flatten() {
442 match attr.key.as_ref() {
443 b"rdf:resource" => res = Some(strip_fragment(std::str::from_utf8(&attr.value)?)),
444 b"xml:lang" => lang = Some(std::str::from_utf8(&attr.value)?.to_string()),
445 _ => {}
446 }
447 }
448 Ok((res, lang))
449}
450
451fn find_resource(
453 attrs: quick_xml::events::attributes::Attributes<'_>,
454) -> Result<Option<String>, Box<dyn std::error::Error>> {
455 for attr in attrs.flatten() {
456 let key = std::str::from_utf8(attr.key.as_ref())?;
457 if key == "rdf:resource" {
458 return Ok(Some(strip_fragment(std::str::from_utf8(&attr.value)?)));
459 }
460 }
461 Ok(None)
462}