1use std::path::Path;
2
3use quick_xml::events::{BytesStart, Event};
4use quick_xml::Reader;
5
6use crate::base::{intern, AttrDef, AttrKind, ClassDef, Element, FastMap, FastSet, FieldValue, TypeRegistry};
7use crate::registry;
8
9pub fn is_cim(text: &str) -> bool {
15 let mut end = text.len().min(8192);
16 while !text.is_char_boundary(end) {
17 end -= 1;
18 }
19 let head = &text[..end];
20 ["http://iec.ch/TC57/", "https://cim.ucaiug.io/ns#", "https://ap.cim4.eu/", "https://energy.referencedata.eu/"]
21 .iter()
22 .any(|marker| head.contains(marker))
23}
24
25#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
29pub struct FieldForm {
30 pub ns: Option<&'static str>,
35 pub datatype: Option<&'static str>,
38 pub resource_ns: Option<&'static str>,
43}
44
45impl FieldForm {
46 fn fill_from(&mut self, other: FieldForm) {
47 self.ns = self.ns.or(other.ns);
48 self.datatype = self.datatype.or(other.datatype);
49 self.resource_ns = self.resource_ns.or(other.resource_ns);
50 }
51}
52
53pub struct CimDataset {
54 pub entries: FastMap<String, Element>,
55 pub by_type: FastMap<String, Vec<String>>,
57 pub field_forms: FastMap<(&'static str, &'static str), FieldForm>,
64 pub type_ns: FastMap<String, &'static str>,
69 pub prefixes: FastMap<&'static str, &'static str>,
73 pub foreign: FastMap<String, Element>,
81}
82
83impl Default for CimDataset {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89impl CimDataset {
90 pub fn new() -> Self {
91 Self {
92 entries: FastMap::default(),
93 by_type: FastMap::default(),
94 field_forms: FastMap::default(),
95 type_ns: FastMap::default(),
96 prefixes: FastMap::default(),
97 foreign: FastMap::default(),
98 }
99 }
100
101 pub fn decode_str(content: &str) -> Result<Self, Box<dyn std::error::Error>> {
103 let mut ds = Self::new();
104 parse_rdf(content, registry::type_registry(), &mut ds)?;
105 Ok(ds)
106 }
107
108 pub fn decode_file(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
110 Self::decode_str(&std::fs::read_to_string(path)?)
111 }
112
113 pub fn decode_files(paths: &[&Path]) -> Result<Self, Box<dyn std::error::Error>> {
115 let mut combined = Self::new();
116 for path in paths {
117 let file_ds = Self::decode_file(path)?;
118 combined.merge(file_ds);
119 }
120 Ok(combined)
121 }
122
123 pub fn decode_files_parallel_separate(paths: &[&Path]) -> Result<Vec<Self>, Box<dyn std::error::Error>> {
127 if paths.len() <= 1 || cfg!(target_family = "wasm") {
129 return paths.iter().map(|p| Self::decode_file(p)).collect();
130 }
131 let results: Vec<Result<Self, String>> = std::thread::scope(|s| {
132 paths
133 .iter()
134 .map(|p| s.spawn(|| Self::decode_file(p).map_err(|e| e.to_string())))
135 .collect::<Vec<_>>()
136 .into_iter()
137 .map(|h| h.join().expect("decode thread panicked"))
138 .collect()
139 });
140 results
141 .into_iter()
142 .collect::<Result<_, String>>()
143 .map_err(|e| e.into())
144 }
145
146 pub fn decode_files_parallel(paths: &[&Path]) -> Result<Self, Box<dyn std::error::Error>> {
148 let datasets = Self::decode_files_parallel_separate(paths)?;
149 Ok(datasets
150 .into_iter()
151 .reduce(|mut a, b| {
152 a.merge(b);
153 a
154 })
155 .unwrap_or_default())
156 }
157
158 pub fn merge(&mut self, other: CimDataset) {
168 for (key, form) in other.field_forms {
169 self.field_forms.entry(key).or_default().fill_from(form);
170 }
171 for (mrid, ns) in other.type_ns {
172 self.type_ns.entry(mrid).or_insert(ns);
173 }
174 for (ns, prefix) in other.prefixes {
175 self.prefixes.entry(ns).or_insert(prefix);
176 }
177 for (id, incoming) in other.foreign {
178 match self.foreign.get_mut(&id) {
179 Some(existing) => existing.merge_from(&incoming),
180 None => {
181 self.foreign.insert(id, incoming);
182 }
183 }
184 }
185 let reg = registry::type_registry();
186 let mut moved: FastMap<&'static str, FastSet<String>> = FastMap::default();
187 for (mrid, incoming) in other.entries {
188 if let Some(existing) = self.entries.get_mut(&mrid) {
189 existing.merge_from(&incoming);
190 let old = existing.class();
191 let mut changed = false;
192 for t in incoming.types() {
193 changed |= existing.add_type(t, reg);
194 }
195 if changed {
196 moved.entry(old.qualified).or_default().insert(mrid.clone());
197 self.by_type.entry(existing.type_name().to_string()).or_default().push(mrid);
198 }
199 } else {
200 let type_name = incoming.type_name().to_string();
201 self.by_type.entry(type_name).or_default().push(mrid.clone());
202 self.entries.insert(mrid, incoming);
203 }
204 }
205 for (old, mrids) in moved {
207 if let Some(bucket) = self.by_type.get_mut(old) {
208 bucket.retain(|m| !mrids.contains(m));
209 }
210 }
211 }
212
213 pub fn element_from_json(&self, class: &'static ClassDef, value: &serde_json::Value) -> Result<Element, String> {
221 let reg = registry::type_registry();
222 let mut element = Element::from_json(class, reg, value)?;
223 let keys: Vec<&'static str> = element.fields().keys().copied().collect();
224 for key in keys {
225 if let Some(reference) = self.held_as_reference(class.qualified, key) {
226 element.retype(key, reference);
227 }
228 }
229 Ok(element)
230 }
231
232 fn held_as_reference(&self, type_name: &str, key: &str) -> Option<bool> {
236 self.by_type
237 .get(type_name)
238 .into_iter()
239 .flatten()
240 .filter_map(|m| self.entries.get(m))
241 .find_map(|e| match e.get(key)? {
242 FieldValue::Resource(_) | FieldValue::ResourceList(_) => Some(true),
243 FieldValue::Text(_) | FieldValue::TextList(_) => Some(false),
244 })
245 .or_else(|| self.field_forms.get(&(type_name, key)).and_then(|f| f.resource_ns).map(|_| true))
246 }
247
248 pub fn set(&mut self, mrid: String, element: Element) {
251 let new_type = element.type_name().to_string();
252 if let Some(old) = self.entries.get(&mrid) {
253 let old_type = old.type_name();
254 if old_type != new_type
255 && let Some(v) = self.by_type.get_mut(old_type) {
256 v.retain(|m| m != &mrid);
257 }
258 }
259 let already_indexed = self
260 .by_type
261 .get(&new_type)
262 .is_some_and(|v| v.contains(&mrid));
263 if !already_indexed {
264 self.by_type.entry(new_type).or_default().push(mrid.clone());
265 }
266 self.entries.insert(mrid, element);
267 }
268
269 pub fn remove(&mut self, mrid: &str) -> Option<Element> {
272 let removed = self.entries.remove(mrid)?;
273 if let Some(v) = self.by_type.get_mut(removed.type_name()) {
274 v.retain(|m| m != mrid);
275 }
276 Some(removed)
277 }
278
279 pub fn type_conflicts(&self) -> Vec<(&str, Vec<&'static str>)> {
285 let reg = registry::type_registry();
286 let related = |a: &ClassDef, b: &ClassDef| {
287 reg.chain(a).iter().any(|c| c.qualified == b.qualified)
288 || reg.chain(b).iter().any(|c| c.qualified == a.qualified)
289 };
290 let mut out: Vec<(&str, Vec<&'static str>)> = self
291 .entries
292 .iter()
293 .filter_map(|(mrid, e)| {
294 let types: Vec<&'static ClassDef> = e.types().collect();
295 let conflict = types.iter().enumerate().any(|(i, a)| types[i + 1..].iter().any(|b| !related(a, b)));
296 conflict.then(|| (mrid.as_str(), types.iter().map(|c| c.qualified).collect()))
297 })
298 .collect();
299 out.sort_unstable();
300 out
301 }
302}
303
304fn index(by_type: &mut FastMap<String, Vec<String>>, type_name: &'static str, mrid: String) {
309 if let Some(bucket) = by_type.get_mut(type_name) {
310 bucket.push(mrid);
311 } else {
312 by_type.insert(type_name.to_string(), vec![mrid]);
313 }
314}
315
316type Table = FastMap<&'static str, &'static ClassDef>;
317
318#[derive(Default)]
324struct Scope {
325 prefixes: Vec<(Vec<u8>, &'static str, Option<&'static Table>)>,
327 default: Option<&'static Table>,
328 default_ns: &'static str,
329}
330
331impl Scope {
332 fn absorb(&mut self, e: &BytesStart, reg: &'static TypeRegistry) {
334 for attr in e.attributes().flatten() {
335 let key = attr.key.as_ref();
336 let Some(rest) = key.strip_prefix(b"xmlns".as_slice()) else {
337 continue;
338 };
339 let Ok(ns) = std::str::from_utf8(&attr.value) else {
340 continue;
341 };
342 let table = reg.ns_table(ns).or_else(|| {
344 if ns.ends_with('#') || ns.ends_with('/') {
345 None
346 } else {
347 reg.ns_table(&format!("{ns}#"))
348 }
349 });
350 let ns = intern(ns);
351 match rest.strip_prefix(b":".as_slice()) {
352 Some(prefix) => {
353 let prefix = prefix.to_vec();
354 match self.prefixes.iter_mut().find(|(k, _, _)| *k == prefix) {
355 Some(slot) => (slot.1, slot.2) = (ns, table),
356 None => self.prefixes.push((prefix, ns, table)),
357 }
358 }
359 None if rest.is_empty() => (self.default, self.default_ns) = (table, ns),
360 None => {}
361 }
362 }
363 }
364
365 fn table(&self, prefix: Option<&[u8]>) -> Option<&'static Table> {
366 match prefix {
367 None => self.default,
368 Some(p) => self
369 .prefixes
370 .iter()
371 .find(|(k, _, _)| k.as_slice() == p)
372 .and_then(|(_, _, t)| *t),
373 }
374 }
375
376 fn ns(&self, prefix: Option<&[u8]>) -> &'static str {
378 match prefix {
379 None => self.default_ns,
380 Some(p) => self.prefixes.iter().find(|(k, _, _)| k.as_slice() == p).map_or("", |(_, ns, _)| ns),
381 }
382 }
383
384}
385
386struct Subject {
388 mrid: String,
391 id: String,
394 described: bool,
396 has_xmlns: bool,
397}
398
399fn scan_attrs(e: &BytesStart) -> Result<Subject, Box<dyn std::error::Error>> {
403 let mut subject = Subject { mrid: String::new(), id: String::new(), described: false, has_xmlns: false };
404 let mut defined = false;
405 for attr in e.attributes().flatten() {
406 let key = attr.key.as_ref();
407 if key == b"rdf:about" || key == b"rdf:ID" {
408 let value = std::str::from_utf8(&attr.value)?;
409 subject.mrid = strip_fragment(value);
410 subject.id = value.strip_prefix('#').unwrap_or(value).to_string();
411 subject.described |= key == b"rdf:about";
412 defined |= key == b"rdf:ID";
413 } else if key == b"rdf:nodeID" {
414 subject.mrid = format!("_:{}", std::str::from_utf8(&attr.value)?);
415 subject.id = subject.mrid.clone();
416 } else if key.starts_with(b"xmlns") {
417 subject.has_xmlns = true;
418 }
419 }
420 subject.described &= !defined;
421 Ok(subject)
422}
423
424fn object(
429 scope: &Scope,
430 reg: &'static TypeRegistry,
431 e: &BytesStart,
432 subject: Subject,
433) -> Result<Option<(Element, bool, Option<&'static str>)>, Box<dyn std::error::Error>> {
434 if subject.id.is_empty() {
435 return Ok(None);
436 }
437 let name = e.name();
438 let (prefix, local) = split_qname(name.as_ref());
439 let ns = scope.ns(prefix);
440 let (mut element, foreign) = match resolve(scope, reg, name.as_ref())? {
441 Some(class) => (Element::new(class, subject.mrid), false),
442 None => (Element::new(crate::base::foreign_class(ns, std::str::from_utf8(local)?), subject.id), true),
443 };
444 if subject.described {
445 element.mark_described();
446 }
447 let written = (!ns.is_empty() && !same_ns(element.type_ns(), ns)).then_some(ns);
448 Ok(Some((element, foreign, written)))
449}
450
451fn same_ns(a: &str, b: &str) -> bool {
454 a == b || a.strip_suffix('#') == Some(b) || b.strip_suffix('#') == Some(a)
455}
456
457fn store(ds: &mut CimDataset, element: Element, foreign: bool, written: Option<&'static str>) {
459 if let Some(ns) = written
460 && !foreign
461 && !element.mrid().is_empty()
462 {
463 ds.type_ns.entry(element.mrid().to_string()).or_insert(ns);
464 }
465 if foreign {
466 ds.foreign.insert(element.mrid().to_string(), element);
467 } else if !element.mrid().is_empty() {
468 index(&mut ds.by_type, element.type_name(), element.mrid().to_string());
469 ds.entries.insert(element.mrid().to_string(), element);
470 }
471}
472
473fn split_qname(raw: &[u8]) -> (Option<&[u8]>, &[u8]) {
475 match raw.iter().position(|b| *b == b':') {
476 Some(i) => (Some(&raw[..i]), &raw[i + 1..]),
477 None => (None, raw),
478 }
479}
480
481fn resolve(
491 scope: &Scope,
492 reg: &'static TypeRegistry,
493 raw: &[u8],
494) -> Result<Option<&'static ClassDef>, Box<dyn std::error::Error>> {
495 let (prefix, local_bytes) = split_qname(raw);
496 let local = std::str::from_utf8(local_bytes)?;
497 if let Some(class) = scope.table(prefix).and_then(|t| t.get(local)) {
498 return Ok(Some(*class));
499 }
500 Ok(reg.bare().get(local).copied())
501}
502
503fn parse_rdf(
505 content: &str,
506 reg: &'static TypeRegistry,
507 ds: &mut CimDataset,
508) -> Result<(), Box<dyn std::error::Error>> {
509 let mut reader = Reader::from_str(content);
510 let mut buf = Vec::new();
511
512 let mut depth: u32 = 0;
513 let mut scope = Scope::default();
514 let mut nested: Option<Scope> = None;
516 let mut current: Option<Element> = None;
519 let mut current_foreign = false;
521 let mut current_ns: Option<&'static str> = None;
523 let mut declared: Option<&'static FastMap<&'static str, &'static AttrDef>> = None;
525 let mut pending_key: Option<&'static str> = None;
526 let mut pending_lang: Option<String> = None;
528 let mut pending_text = String::new();
532 let field = |declared: Option<&'static FastMap<&'static str, &'static AttrDef>>,
538 class: &'static str,
539 e: &BytesStart,
540 scope: &Scope,
541 forms: &mut FastMap<(&'static str, &'static str), FieldForm>|
542 -> Result<(&'static str, FieldAttrs), Box<dyn std::error::Error>> {
543 let name = e.name();
544 let (prefix, local_bytes) = split_qname(name.as_ref());
545 let local = std::str::from_utf8(local_bytes)?;
546 let attr = declared.and_then(|d| d.get(local));
547 let key = attr.map_or_else(|| intern(local), |a| a.id);
548 let attrs = field_attrs(e.attributes())?;
549 let ns = scope.ns(prefix);
550 let undeclared_ns = (!ns.is_empty() && attr.is_none_or(|a| !same_ns(a.ns, ns))).then_some(ns);
551 let resource_ns = attrs.resource_ns.filter(|_| attr.is_none_or(|a| a.kind != AttrKind::Enum));
552 if undeclared_ns.is_some() || attrs.datatype.is_some() || resource_ns.is_some() {
553 forms.entry((class, key)).or_default().fill_from(FieldForm {
554 ns: undeclared_ns,
555 datatype: attrs.datatype,
556 resource_ns,
557 });
558 }
559 Ok((key, attrs))
560 };
561
562 loop {
563 match reader.read_event_into(&mut buf) {
564 Ok(Event::Start(ref e)) => {
565 depth += 1;
566
567 match depth {
568 1 => {
569 scope.absorb(e, reg);
570 for (prefix, ns, _) in &scope.prefixes {
571 if let Ok(prefix) = std::str::from_utf8(prefix) {
572 ds.prefixes.entry(ns).or_insert_with(|| intern(prefix));
573 }
574 }
575 }
576 2 => {
577 let subject = scan_attrs(e)?;
582 if subject.has_xmlns {
583 let mut inner = Scope {
584 prefixes: scope.prefixes.clone(),
585 default: scope.default,
586 default_ns: scope.default_ns,
587 };
588 inner.absorb(e, reg);
589 nested = Some(std::mem::replace(&mut scope, inner));
590 }
591
592 (current, current_foreign, current_ns) = match object(&scope, reg, e, subject)? {
593 Some((element, foreign, written)) => (Some(element), foreign, written),
594 None => (None, false, None),
595 };
596 declared = current.as_ref().and_then(|c| reg.declared(c.class()));
597 }
598 3 => {
599 if let Some(element) = current.as_mut() {
600 let (key, attrs) = field(declared, element.type_name(), e, &scope, &mut ds.field_forms)?;
601 if let Some(res) = attrs.resource {
602 element.add_field(key, FieldValue::Resource(res));
603 } else if !attrs.nodes {
604 pending_key = Some(key);
605 pending_lang = attrs.lang;
606 pending_text.clear();
607 }
608 }
609 }
610 _ => pending_key = None,
613 }
614 }
615
616 Ok(Event::Empty(ref e)) => {
617 match depth {
618 1 => {
619 let subject = scan_attrs(e)?;
624 let local_scope = if subject.has_xmlns {
625 let mut inner = Scope {
626 prefixes: scope.prefixes.clone(),
627 default: scope.default,
628 default_ns: scope.default_ns,
629 };
630 inner.absorb(e, reg);
631 Some(inner)
632 } else {
633 None
634 };
635 let active = local_scope.as_ref().unwrap_or(&scope);
636 if let Some((element, foreign, written)) = object(active, reg, e, subject)? {
637 store(ds, element, foreign, written);
638 }
639 }
640 2 => {
641 if let Some(element) = current.as_mut() {
644 let (key, attrs) = field(declared, element.type_name(), e, &scope, &mut ds.field_forms)?;
645 if let Some(res) = attrs.resource {
646 element.add_field(key, FieldValue::Resource(res));
647 } else if !attrs.nodes {
648 element.add_field(key, FieldValue::Text(String::new()));
649 if let Some(lang) = attrs.lang {
650 element.add_lang(key, &lang);
651 }
652 }
653 }
654 }
655 _ => pending_key = None,
656 }
657 }
658
659 Ok(Event::Text(ref e)) if depth == 3 && pending_key.is_some() => {
660 pending_text.push_str(&e.unescape()?);
661 }
662
663 Ok(Event::CData(ref e)) if depth == 3 && pending_key.is_some() => {
664 pending_text.push_str(std::str::from_utf8(e)?);
665 }
666
667 Ok(Event::End(_)) => {
668 if depth == 3
669 && let (Some(element), Some(key)) = (current.as_mut(), pending_key.take()) {
670 element.add_field(key, FieldValue::Text(std::mem::take(&mut pending_text)));
671 if let Some(lang) = pending_lang.take() {
672 element.add_lang(key, &lang);
673 }
674 }
675 if depth == 2 {
676 pending_key = None;
677 pending_lang = None;
678 if let Some(element) = current.take() {
679 store(ds, element, current_foreign, current_ns.take());
680 }
681 if let Some(outer) = nested.take() {
682 scope = outer;
683 }
684 }
685 depth = depth.saturating_sub(1);
686 }
687
688 Ok(Event::Eof) => break,
689 Err(e) => return Err(Box::new(e)),
690 _ => {}
691 }
692 buf.clear();
693 }
694
695 Ok(())
696}
697
698fn strip_fragment(s: &str) -> String {
708 match s.rfind('#') {
709 Some(i) if i + 1 == s.len() && i > 0 => s.to_string(),
710 Some(i) => s[i + 1..].to_string(),
711 None => s.to_string(),
712 }
713}
714
715
716struct FieldAttrs {
718 resource: Option<String>,
720 resource_ns: Option<&'static str>,
723 lang: Option<String>,
724 datatype: Option<&'static str>,
725 nodes: bool,
729}
730
731fn field_attrs(attrs: quick_xml::events::attributes::Attributes<'_>) -> Result<FieldAttrs, Box<dyn std::error::Error>> {
733 let mut out = FieldAttrs { resource: None, resource_ns: None, lang: None, datatype: None, nodes: false };
734 for attr in attrs.flatten() {
735 match attr.key.as_ref() {
736 b"rdf:resource" => {
737 let iri = std::str::from_utf8(&attr.value)?;
738 let kept = strip_fragment(iri);
739 if kept.len() < iri.len() && kept.len() + 1 < iri.len() {
740 out.resource_ns = Some(intern(&iri[..iri.len() - kept.len()]));
741 }
742 out.resource = Some(kept);
743 }
744 b"rdf:nodeID" => out.resource = Some(format!("_:{}", std::str::from_utf8(&attr.value)?)),
745 b"xml:lang" => out.lang = Some(std::str::from_utf8(&attr.value)?.to_string()),
746 b"rdf:datatype" => out.datatype = Some(intern(std::str::from_utf8(&attr.value)?)),
747 b"rdf:parseType" => out.nodes = attr.value.as_ref() != b"Literal",
748 _ => {}
749 }
750 }
751 Ok(out)
752}