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 {
128 return paths.iter().map(|p| Self::decode_file(p)).collect();
129 }
130 let results: Vec<Result<Self, String>> = std::thread::scope(|s| {
131 paths
132 .iter()
133 .map(|p| s.spawn(|| Self::decode_file(p).map_err(|e| e.to_string())))
134 .collect::<Vec<_>>()
135 .into_iter()
136 .map(|h| h.join().expect("decode thread panicked"))
137 .collect()
138 });
139 results
140 .into_iter()
141 .collect::<Result<_, String>>()
142 .map_err(|e| e.into())
143 }
144
145 pub fn decode_files_parallel(paths: &[&Path]) -> Result<Self, Box<dyn std::error::Error>> {
147 let datasets = Self::decode_files_parallel_separate(paths)?;
148 Ok(datasets
149 .into_iter()
150 .reduce(|mut a, b| {
151 a.merge(b);
152 a
153 })
154 .unwrap_or_default())
155 }
156
157 pub fn merge(&mut self, other: CimDataset) {
167 for (key, form) in other.field_forms {
168 self.field_forms.entry(key).or_default().fill_from(form);
169 }
170 for (mrid, ns) in other.type_ns {
171 self.type_ns.entry(mrid).or_insert(ns);
172 }
173 for (ns, prefix) in other.prefixes {
174 self.prefixes.entry(ns).or_insert(prefix);
175 }
176 for (id, incoming) in other.foreign {
177 match self.foreign.get_mut(&id) {
178 Some(existing) => existing.merge_from(&incoming),
179 None => {
180 self.foreign.insert(id, incoming);
181 }
182 }
183 }
184 let reg = registry::type_registry();
185 let mut moved: FastMap<&'static str, FastSet<String>> = FastMap::default();
186 for (mrid, incoming) in other.entries {
187 if let Some(existing) = self.entries.get_mut(&mrid) {
188 existing.merge_from(&incoming);
189 let old = existing.class();
190 let mut changed = false;
191 for t in incoming.types() {
192 changed |= existing.add_type(t, reg);
193 }
194 if changed {
195 moved.entry(old.qualified).or_default().insert(mrid.clone());
196 self.by_type.entry(existing.type_name().to_string()).or_default().push(mrid);
197 }
198 } else {
199 let type_name = incoming.type_name().to_string();
200 self.by_type.entry(type_name).or_default().push(mrid.clone());
201 self.entries.insert(mrid, incoming);
202 }
203 }
204 for (old, mrids) in moved {
206 if let Some(bucket) = self.by_type.get_mut(old) {
207 bucket.retain(|m| !mrids.contains(m));
208 }
209 }
210 }
211
212 pub fn element_from_json(&self, class: &'static ClassDef, value: &serde_json::Value) -> Result<Element, String> {
220 let reg = registry::type_registry();
221 let mut element = Element::from_json(class, reg, value)?;
222 let keys: Vec<&'static str> = element.fields().keys().copied().collect();
223 for key in keys {
224 if let Some(reference) = self.held_as_reference(class.qualified, key) {
225 element.retype(key, reference);
226 }
227 }
228 Ok(element)
229 }
230
231 fn held_as_reference(&self, type_name: &str, key: &str) -> Option<bool> {
235 self.by_type
236 .get(type_name)
237 .into_iter()
238 .flatten()
239 .filter_map(|m| self.entries.get(m))
240 .find_map(|e| match e.get(key)? {
241 FieldValue::Resource(_) | FieldValue::ResourceList(_) => Some(true),
242 FieldValue::Text(_) | FieldValue::TextList(_) => Some(false),
243 })
244 .or_else(|| self.field_forms.get(&(type_name, key)).and_then(|f| f.resource_ns).map(|_| true))
245 }
246
247 pub fn set(&mut self, mrid: String, element: Element) {
250 let new_type = element.type_name().to_string();
251 if let Some(old) = self.entries.get(&mrid) {
252 let old_type = old.type_name();
253 if old_type != new_type
254 && let Some(v) = self.by_type.get_mut(old_type) {
255 v.retain(|m| m != &mrid);
256 }
257 }
258 let already_indexed = self
259 .by_type
260 .get(&new_type)
261 .is_some_and(|v| v.contains(&mrid));
262 if !already_indexed {
263 self.by_type.entry(new_type).or_default().push(mrid.clone());
264 }
265 self.entries.insert(mrid, element);
266 }
267
268 pub fn remove(&mut self, mrid: &str) -> Option<Element> {
271 let removed = self.entries.remove(mrid)?;
272 if let Some(v) = self.by_type.get_mut(removed.type_name()) {
273 v.retain(|m| m != mrid);
274 }
275 Some(removed)
276 }
277
278 pub fn type_conflicts(&self) -> Vec<(&str, Vec<&'static str>)> {
284 let reg = registry::type_registry();
285 let related = |a: &ClassDef, b: &ClassDef| {
286 reg.chain(a).iter().any(|c| c.qualified == b.qualified)
287 || reg.chain(b).iter().any(|c| c.qualified == a.qualified)
288 };
289 let mut out: Vec<(&str, Vec<&'static str>)> = self
290 .entries
291 .iter()
292 .filter_map(|(mrid, e)| {
293 let types: Vec<&'static ClassDef> = e.types().collect();
294 let conflict = types.iter().enumerate().any(|(i, a)| types[i + 1..].iter().any(|b| !related(a, b)));
295 conflict.then(|| (mrid.as_str(), types.iter().map(|c| c.qualified).collect()))
296 })
297 .collect();
298 out.sort_unstable();
299 out
300 }
301}
302
303fn index(by_type: &mut FastMap<String, Vec<String>>, type_name: &'static str, mrid: String) {
308 if let Some(bucket) = by_type.get_mut(type_name) {
309 bucket.push(mrid);
310 } else {
311 by_type.insert(type_name.to_string(), vec![mrid]);
312 }
313}
314
315type Table = FastMap<&'static str, &'static ClassDef>;
316
317#[derive(Default)]
323struct Scope {
324 prefixes: Vec<(Vec<u8>, &'static str, Option<&'static Table>)>,
326 default: Option<&'static Table>,
327 default_ns: &'static str,
328}
329
330impl Scope {
331 fn absorb(&mut self, e: &BytesStart, reg: &'static TypeRegistry) {
333 for attr in e.attributes().flatten() {
334 let key = attr.key.as_ref();
335 let Some(rest) = key.strip_prefix(b"xmlns".as_slice()) else {
336 continue;
337 };
338 let Ok(ns) = std::str::from_utf8(&attr.value) else {
339 continue;
340 };
341 let table = reg.ns_table(ns).or_else(|| {
343 if ns.ends_with('#') || ns.ends_with('/') {
344 None
345 } else {
346 reg.ns_table(&format!("{ns}#"))
347 }
348 });
349 let ns = intern(ns);
350 match rest.strip_prefix(b":".as_slice()) {
351 Some(prefix) => {
352 let prefix = prefix.to_vec();
353 match self.prefixes.iter_mut().find(|(k, _, _)| *k == prefix) {
354 Some(slot) => (slot.1, slot.2) = (ns, table),
355 None => self.prefixes.push((prefix, ns, table)),
356 }
357 }
358 None if rest.is_empty() => (self.default, self.default_ns) = (table, ns),
359 None => {}
360 }
361 }
362 }
363
364 fn table(&self, prefix: Option<&[u8]>) -> Option<&'static Table> {
365 match prefix {
366 None => self.default,
367 Some(p) => self
368 .prefixes
369 .iter()
370 .find(|(k, _, _)| k.as_slice() == p)
371 .and_then(|(_, _, t)| *t),
372 }
373 }
374
375 fn ns(&self, prefix: Option<&[u8]>) -> &'static str {
377 match prefix {
378 None => self.default_ns,
379 Some(p) => self.prefixes.iter().find(|(k, _, _)| k.as_slice() == p).map_or("", |(_, ns, _)| ns),
380 }
381 }
382
383}
384
385struct Subject {
387 mrid: String,
390 id: String,
393 described: bool,
395 has_xmlns: bool,
396}
397
398fn scan_attrs(e: &BytesStart) -> Result<Subject, Box<dyn std::error::Error>> {
402 let mut subject = Subject { mrid: String::new(), id: String::new(), described: false, has_xmlns: false };
403 let mut defined = false;
404 for attr in e.attributes().flatten() {
405 let key = attr.key.as_ref();
406 if key == b"rdf:about" || key == b"rdf:ID" {
407 let value = std::str::from_utf8(&attr.value)?;
408 subject.mrid = strip_fragment(value);
409 subject.id = value.strip_prefix('#').unwrap_or(value).to_string();
410 subject.described |= key == b"rdf:about";
411 defined |= key == b"rdf:ID";
412 } else if key == b"rdf:nodeID" {
413 subject.mrid = format!("_:{}", std::str::from_utf8(&attr.value)?);
414 subject.id = subject.mrid.clone();
415 } else if key.starts_with(b"xmlns") {
416 subject.has_xmlns = true;
417 }
418 }
419 subject.described &= !defined;
420 Ok(subject)
421}
422
423fn object(
428 scope: &Scope,
429 reg: &'static TypeRegistry,
430 e: &BytesStart,
431 subject: Subject,
432) -> Result<Option<(Element, bool, Option<&'static str>)>, Box<dyn std::error::Error>> {
433 if subject.id.is_empty() {
434 return Ok(None);
435 }
436 let name = e.name();
437 let (prefix, local) = split_qname(name.as_ref());
438 let ns = scope.ns(prefix);
439 let (mut element, foreign) = match resolve(scope, reg, name.as_ref())? {
440 Some(class) => (Element::new(class, subject.mrid), false),
441 None => (Element::new(crate::base::foreign_class(ns, std::str::from_utf8(local)?), subject.id), true),
442 };
443 if subject.described {
444 element.mark_described();
445 }
446 let written = (!ns.is_empty() && !same_ns(element.type_ns(), ns)).then_some(ns);
447 Ok(Some((element, foreign, written)))
448}
449
450fn same_ns(a: &str, b: &str) -> bool {
453 a == b || a.strip_suffix('#') == Some(b) || b.strip_suffix('#') == Some(a)
454}
455
456fn store(ds: &mut CimDataset, element: Element, foreign: bool, written: Option<&'static str>) {
458 if let Some(ns) = written
459 && !foreign
460 && !element.mrid().is_empty()
461 {
462 ds.type_ns.entry(element.mrid().to_string()).or_insert(ns);
463 }
464 if foreign {
465 ds.foreign.insert(element.mrid().to_string(), element);
466 } else if !element.mrid().is_empty() {
467 index(&mut ds.by_type, element.type_name(), element.mrid().to_string());
468 ds.entries.insert(element.mrid().to_string(), element);
469 }
470}
471
472fn split_qname(raw: &[u8]) -> (Option<&[u8]>, &[u8]) {
474 match raw.iter().position(|b| *b == b':') {
475 Some(i) => (Some(&raw[..i]), &raw[i + 1..]),
476 None => (None, raw),
477 }
478}
479
480fn resolve(
490 scope: &Scope,
491 reg: &'static TypeRegistry,
492 raw: &[u8],
493) -> Result<Option<&'static ClassDef>, Box<dyn std::error::Error>> {
494 let (prefix, local_bytes) = split_qname(raw);
495 let local = std::str::from_utf8(local_bytes)?;
496 if let Some(class) = scope.table(prefix).and_then(|t| t.get(local)) {
497 return Ok(Some(*class));
498 }
499 Ok(reg.bare().get(local).copied())
500}
501
502fn parse_rdf(
504 content: &str,
505 reg: &'static TypeRegistry,
506 ds: &mut CimDataset,
507) -> Result<(), Box<dyn std::error::Error>> {
508 let mut reader = Reader::from_str(content);
509 let mut buf = Vec::new();
510
511 let mut depth: u32 = 0;
512 let mut scope = Scope::default();
513 let mut nested: Option<Scope> = None;
515 let mut current: Option<Element> = None;
518 let mut current_foreign = false;
520 let mut current_ns: Option<&'static str> = None;
522 let mut declared: Option<&'static FastMap<&'static str, &'static AttrDef>> = None;
524 let mut pending_key: Option<&'static str> = None;
525 let mut pending_lang: Option<String> = None;
527 let mut pending_text = String::new();
531 let field = |declared: Option<&'static FastMap<&'static str, &'static AttrDef>>,
537 class: &'static str,
538 e: &BytesStart,
539 scope: &Scope,
540 forms: &mut FastMap<(&'static str, &'static str), FieldForm>|
541 -> Result<(&'static str, FieldAttrs), Box<dyn std::error::Error>> {
542 let name = e.name();
543 let (prefix, local_bytes) = split_qname(name.as_ref());
544 let local = std::str::from_utf8(local_bytes)?;
545 let attr = declared.and_then(|d| d.get(local));
546 let key = attr.map_or_else(|| intern(local), |a| a.id);
547 let attrs = field_attrs(e.attributes())?;
548 let ns = scope.ns(prefix);
549 let undeclared_ns = (!ns.is_empty() && attr.is_none_or(|a| !same_ns(a.ns, ns))).then_some(ns);
550 let resource_ns = attrs.resource_ns.filter(|_| attr.is_none_or(|a| a.kind != AttrKind::Enum));
551 if undeclared_ns.is_some() || attrs.datatype.is_some() || resource_ns.is_some() {
552 forms.entry((class, key)).or_default().fill_from(FieldForm {
553 ns: undeclared_ns,
554 datatype: attrs.datatype,
555 resource_ns,
556 });
557 }
558 Ok((key, attrs))
559 };
560
561 loop {
562 match reader.read_event_into(&mut buf) {
563 Ok(Event::Start(ref e)) => {
564 depth += 1;
565
566 match depth {
567 1 => {
568 scope.absorb(e, reg);
569 for (prefix, ns, _) in &scope.prefixes {
570 if let Ok(prefix) = std::str::from_utf8(prefix) {
571 ds.prefixes.entry(ns).or_insert_with(|| intern(prefix));
572 }
573 }
574 }
575 2 => {
576 let subject = scan_attrs(e)?;
581 if subject.has_xmlns {
582 let mut inner = Scope {
583 prefixes: scope.prefixes.clone(),
584 default: scope.default,
585 default_ns: scope.default_ns,
586 };
587 inner.absorb(e, reg);
588 nested = Some(std::mem::replace(&mut scope, inner));
589 }
590
591 (current, current_foreign, current_ns) = match object(&scope, reg, e, subject)? {
592 Some((element, foreign, written)) => (Some(element), foreign, written),
593 None => (None, false, None),
594 };
595 declared = current.as_ref().and_then(|c| reg.declared(c.class()));
596 }
597 3 => {
598 if let Some(element) = current.as_mut() {
599 let (key, attrs) = field(declared, element.type_name(), e, &scope, &mut ds.field_forms)?;
600 if let Some(res) = attrs.resource {
601 element.add_field(key, FieldValue::Resource(res));
602 } else if !attrs.nodes {
603 pending_key = Some(key);
604 pending_lang = attrs.lang;
605 pending_text.clear();
606 }
607 }
608 }
609 _ => pending_key = None,
612 }
613 }
614
615 Ok(Event::Empty(ref e)) => {
616 match depth {
617 1 => {
618 let subject = scan_attrs(e)?;
623 let local_scope = if subject.has_xmlns {
624 let mut inner = Scope {
625 prefixes: scope.prefixes.clone(),
626 default: scope.default,
627 default_ns: scope.default_ns,
628 };
629 inner.absorb(e, reg);
630 Some(inner)
631 } else {
632 None
633 };
634 let active = local_scope.as_ref().unwrap_or(&scope);
635 if let Some((element, foreign, written)) = object(active, reg, e, subject)? {
636 store(ds, element, foreign, written);
637 }
638 }
639 2 => {
640 if let Some(element) = current.as_mut() {
643 let (key, attrs) = field(declared, element.type_name(), e, &scope, &mut ds.field_forms)?;
644 if let Some(res) = attrs.resource {
645 element.add_field(key, FieldValue::Resource(res));
646 } else if !attrs.nodes {
647 element.add_field(key, FieldValue::Text(String::new()));
648 if let Some(lang) = attrs.lang {
649 element.add_lang(key, &lang);
650 }
651 }
652 }
653 }
654 _ => pending_key = None,
655 }
656 }
657
658 Ok(Event::Text(ref e)) if depth == 3 && pending_key.is_some() => {
659 pending_text.push_str(&e.unescape()?);
660 }
661
662 Ok(Event::CData(ref e)) if depth == 3 && pending_key.is_some() => {
663 pending_text.push_str(std::str::from_utf8(e)?);
664 }
665
666 Ok(Event::End(_)) => {
667 if depth == 3
668 && let (Some(element), Some(key)) = (current.as_mut(), pending_key.take()) {
669 element.add_field(key, FieldValue::Text(std::mem::take(&mut pending_text)));
670 if let Some(lang) = pending_lang.take() {
671 element.add_lang(key, &lang);
672 }
673 }
674 if depth == 2 {
675 pending_key = None;
676 pending_lang = None;
677 if let Some(element) = current.take() {
678 store(ds, element, current_foreign, current_ns.take());
679 }
680 if let Some(outer) = nested.take() {
681 scope = outer;
682 }
683 }
684 depth = depth.saturating_sub(1);
685 }
686
687 Ok(Event::Eof) => break,
688 Err(e) => return Err(Box::new(e)),
689 _ => {}
690 }
691 buf.clear();
692 }
693
694 Ok(())
695}
696
697fn strip_fragment(s: &str) -> String {
707 match s.rfind('#') {
708 Some(i) if i + 1 == s.len() && i > 0 => s.to_string(),
709 Some(i) => s[i + 1..].to_string(),
710 None => s.to_string(),
711 }
712}
713
714
715struct FieldAttrs {
717 resource: Option<String>,
719 resource_ns: Option<&'static str>,
722 lang: Option<String>,
723 datatype: Option<&'static str>,
724 nodes: bool,
728}
729
730fn field_attrs(attrs: quick_xml::events::attributes::Attributes<'_>) -> Result<FieldAttrs, Box<dyn std::error::Error>> {
732 let mut out = FieldAttrs { resource: None, resource_ns: None, lang: None, datatype: None, nodes: false };
733 for attr in attrs.flatten() {
734 match attr.key.as_ref() {
735 b"rdf:resource" => {
736 let iri = std::str::from_utf8(&attr.value)?;
737 let kept = strip_fragment(iri);
738 if kept.len() < iri.len() && kept.len() + 1 < iri.len() {
739 out.resource_ns = Some(intern(&iri[..iri.len() - kept.len()]));
740 }
741 out.resource = Some(kept);
742 }
743 b"rdf:nodeID" => out.resource = Some(format!("_:{}", std::str::from_utf8(&attr.value)?)),
744 b"xml:lang" => out.lang = Some(std::str::from_utf8(&attr.value)?.to_string()),
745 b"rdf:datatype" => out.datatype = Some(intern(std::str::from_utf8(&attr.value)?)),
746 b"rdf:parseType" => out.nodes = attr.value.as_ref() != b"Literal",
747 _ => {}
748 }
749 }
750 Ok(out)
751}