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 fn is_cim(text: &str) -> bool {
12 let mut end = text.len().min(8192);
13 while !text.is_char_boundary(end) {
14 end -= 1;
15 }
16 let head = &text[..end];
17 head.contains("http://iec.ch/TC57/") || head.contains("https://cim.ucaiug.io/ns#")
18}
19
20pub struct CimDataset {
21 pub entries: FastMap<String, Element>,
22 pub by_type: FastMap<String, Vec<String>>,
24}
25
26impl Default for CimDataset {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl CimDataset {
33 pub fn new() -> Self {
34 Self {
35 entries: FastMap::default(),
36 by_type: FastMap::default(),
37 }
38 }
39
40 pub fn decode_str(content: &str) -> Result<Self, Box<dyn std::error::Error>> {
42 let mut ds = Self::new();
43 parse_rdf(content, registry::type_registry(), &mut ds)?;
44 Ok(ds)
45 }
46
47 pub fn decode_file(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
49 Self::decode_str(&std::fs::read_to_string(path)?)
50 }
51
52 pub fn decode_files(paths: &[&Path]) -> Result<Self, Box<dyn std::error::Error>> {
54 let mut combined = Self::new();
55 for path in paths {
56 let file_ds = Self::decode_file(path)?;
57 combined.merge(file_ds);
58 }
59 Ok(combined)
60 }
61
62 pub fn decode_files_parallel_separate(paths: &[&Path]) -> Result<Vec<Self>, Box<dyn std::error::Error>> {
66 if paths.len() <= 1 {
67 return paths.iter().map(|p| Self::decode_file(p)).collect();
68 }
69 let results: Vec<Result<Self, String>> = std::thread::scope(|s| {
70 paths
71 .iter()
72 .map(|p| s.spawn(|| Self::decode_file(p).map_err(|e| e.to_string())))
73 .collect::<Vec<_>>()
74 .into_iter()
75 .map(|h| h.join().expect("decode thread panicked"))
76 .collect()
77 });
78 results
79 .into_iter()
80 .collect::<Result<_, String>>()
81 .map_err(|e| e.into())
82 }
83
84 pub fn decode_files_parallel(paths: &[&Path]) -> Result<Self, Box<dyn std::error::Error>> {
86 let datasets = Self::decode_files_parallel_separate(paths)?;
87 Ok(datasets
88 .into_iter()
89 .reduce(|mut a, b| {
90 a.merge(b);
91 a
92 })
93 .unwrap_or_default())
94 }
95
96 pub fn merge(&mut self, other: CimDataset) {
106 let reg = registry::type_registry();
107 let mut moved: FastMap<&'static str, FastSet<String>> = FastMap::default();
108 for (mrid, incoming) in other.entries {
109 if let Some(existing) = self.entries.get_mut(&mrid) {
110 existing.merge_from(&incoming);
111 let old = existing.class();
112 let mut changed = false;
113 for t in incoming.types() {
114 changed |= existing.add_type(t, reg);
115 }
116 if changed {
117 moved.entry(old.qualified).or_default().insert(mrid.clone());
118 self.by_type.entry(existing.type_name().to_string()).or_default().push(mrid);
119 }
120 } else {
121 let type_name = incoming.type_name().to_string();
122 self.by_type.entry(type_name).or_default().push(mrid.clone());
123 self.entries.insert(mrid, incoming);
124 }
125 }
126 for (old, mrids) in moved {
128 if let Some(bucket) = self.by_type.get_mut(old) {
129 bucket.retain(|m| !mrids.contains(m));
130 }
131 }
132 }
133
134 pub fn set(&mut self, mrid: String, element: Element) {
137 let new_type = element.type_name().to_string();
138 if let Some(old) = self.entries.get(&mrid) {
139 let old_type = old.type_name();
140 if old_type != new_type
141 && let Some(v) = self.by_type.get_mut(old_type) {
142 v.retain(|m| m != &mrid);
143 }
144 }
145 let already_indexed = self
146 .by_type
147 .get(&new_type)
148 .is_some_and(|v| v.contains(&mrid));
149 if !already_indexed {
150 self.by_type.entry(new_type).or_default().push(mrid.clone());
151 }
152 self.entries.insert(mrid, element);
153 }
154
155 pub fn remove(&mut self, mrid: &str) -> Option<Element> {
158 let removed = self.entries.remove(mrid)?;
159 if let Some(v) = self.by_type.get_mut(removed.type_name()) {
160 v.retain(|m| m != mrid);
161 }
162 Some(removed)
163 }
164}
165
166fn index(by_type: &mut FastMap<String, Vec<String>>, type_name: &'static str, mrid: String) {
171 if let Some(bucket) = by_type.get_mut(type_name) {
172 bucket.push(mrid);
173 } else {
174 by_type.insert(type_name.to_string(), vec![mrid]);
175 }
176}
177
178type Table = FastMap<&'static str, &'static ClassDef>;
179
180#[derive(Default)]
186struct Scope {
187 prefixes: Vec<(Vec<u8>, Option<&'static Table>)>,
188 default: Option<&'static Table>,
189}
190
191impl Scope {
192 fn absorb(&mut self, e: &BytesStart, reg: &'static TypeRegistry) {
194 for attr in e.attributes().flatten() {
195 let key = attr.key.as_ref();
196 let Some(rest) = key.strip_prefix(b"xmlns".as_slice()) else {
197 continue;
198 };
199 let Ok(ns) = std::str::from_utf8(&attr.value) else {
200 continue;
201 };
202 let table = reg.ns_table(ns).or_else(|| {
204 if ns.ends_with('#') || ns.ends_with('/') {
205 None
206 } else {
207 reg.ns_table(&format!("{ns}#"))
208 }
209 });
210 match rest.strip_prefix(b":".as_slice()) {
211 Some(prefix) => {
212 let prefix = prefix.to_vec();
213 match self.prefixes.iter_mut().find(|(k, _)| *k == prefix) {
214 Some(slot) => slot.1 = table,
215 None => self.prefixes.push((prefix, table)),
216 }
217 }
218 None if rest.is_empty() => self.default = table,
219 None => {}
220 }
221 }
222 }
223
224 fn table(&self, prefix: Option<&[u8]>) -> Option<&'static Table> {
225 match prefix {
226 None => self.default,
227 Some(p) => self
228 .prefixes
229 .iter()
230 .find(|(k, _)| k.as_slice() == p)
231 .and_then(|(_, t)| *t),
232 }
233 }
234
235}
236
237fn scan_attrs(e: &BytesStart) -> Result<(String, bool, bool), Box<dyn std::error::Error>> {
243 let mut mrid = String::new();
244 let mut described = false;
245 let mut defined = false;
246 let mut has_xmlns = false;
247 for attr in e.attributes().flatten() {
248 let key = attr.key.as_ref();
249 if key == b"rdf:about" || key == b"rdf:ID" {
250 mrid = strip_fragment(std::str::from_utf8(&attr.value)?);
251 described |= key == b"rdf:about";
252 defined |= key == b"rdf:ID";
253 } else if key.starts_with(b"xmlns") {
254 has_xmlns = true;
255 }
256 }
257 Ok((mrid, described && !defined, has_xmlns))
258}
259
260fn split_qname(raw: &[u8]) -> (Option<&[u8]>, &[u8]) {
262 match raw.iter().position(|b| *b == b':') {
263 Some(i) => (Some(&raw[..i]), &raw[i + 1..]),
264 None => (None, raw),
265 }
266}
267
268fn resolve(
278 scope: &Scope,
279 reg: &'static TypeRegistry,
280 raw: &[u8],
281) -> Result<Option<&'static ClassDef>, Box<dyn std::error::Error>> {
282 let (prefix, local_bytes) = split_qname(raw);
283 let local = std::str::from_utf8(local_bytes)?;
284 if let Some(class) = scope.table(prefix).and_then(|t| t.get(local)) {
285 return Ok(Some(*class));
286 }
287 Ok(reg.bare().get(local).copied())
288}
289
290fn parse_rdf(
292 content: &str,
293 reg: &'static TypeRegistry,
294 ds: &mut CimDataset,
295) -> Result<(), Box<dyn std::error::Error>> {
296 let mut reader = Reader::from_str(content);
297 let mut buf = Vec::new();
298
299 let mut depth: u32 = 0;
300 let mut scope = Scope::default();
301 let mut nested: Option<Scope> = None;
303 let mut current: Option<Element> = None;
306 let mut declared: Option<&'static FastMap<&'static str, &'static AttrDef>> = None;
308 let mut pending_key: Option<&'static str> = None;
309 let mut pending_lang: Option<String> = None;
311 let key_of = |declared: Option<&'static FastMap<&'static str, &'static AttrDef>>, local: &str| {
314 declared.and_then(|d| d.get(local)).map_or_else(|| intern(local), |a| a.id)
315 };
316
317 loop {
318 match reader.read_event_into(&mut buf) {
319 Ok(Event::Start(ref e)) => {
320 depth += 1;
321
322 match depth {
323 1 => scope.absorb(e, reg),
324 2 => {
325 let (mrid, described, has_xmlns) = scan_attrs(e)?;
330 if has_xmlns {
331 let mut inner = Scope {
332 prefixes: scope.prefixes.clone(),
333 default: scope.default,
334 };
335 inner.absorb(e, reg);
336 nested = Some(std::mem::replace(&mut scope, inner));
337 }
338
339 current = resolve(&scope, reg, e.name().as_ref())?.map(|class| {
340 let mut element = Element::new(class, mrid);
341 if described {
342 element.mark_described();
343 }
344 element
345 });
346 declared = current.as_ref().and_then(|c| reg.declared(c.class()));
347 }
348 3 => {
349 if let Some(element) = current.as_mut() {
350 let name = e.name();
351 let (_, local_bytes) = split_qname(name.as_ref());
352 let local = key_of(declared, std::str::from_utf8(local_bytes)?);
353 let (res, lang) = field_attrs(e.attributes())?;
354 if let Some(res) = res {
355 element.add_field(local, FieldValue::Resource(res));
356 } else {
357 pending_key = Some(local);
358 pending_lang = lang;
359 }
360 }
361 }
362 _ => {}
363 }
364 }
365
366 Ok(Event::Empty(ref e)) => {
367 match depth {
368 1 => {
369 let (mrid, described, has_xmlns) = scan_attrs(e)?;
374 let local_scope = if has_xmlns {
375 let mut inner = Scope {
376 prefixes: scope.prefixes.clone(),
377 default: scope.default,
378 };
379 inner.absorb(e, reg);
380 Some(inner)
381 } else {
382 None
383 };
384 let active = local_scope.as_ref().unwrap_or(&scope);
385 if !mrid.is_empty()
386 && let Some(class) = resolve(active, reg, e.name().as_ref())? {
387 index(&mut ds.by_type, class.qualified, mrid.clone());
388 let mut element = Element::new(class, mrid.clone());
389 if described {
390 element.mark_described();
391 }
392 ds.entries.insert(mrid, element);
393 }
394 }
395 2 => {
396 if let Some(element) = current.as_mut()
398 && let Some(res) = find_resource(e.attributes())? {
399 let name = e.name();
400 let (_, local_bytes) = split_qname(name.as_ref());
401 let local = key_of(declared, std::str::from_utf8(local_bytes)?);
402 element.add_field(local, FieldValue::Resource(res));
403 }
404 }
405 _ => {}
406 }
407 }
408
409 Ok(Event::Text(ref e)) => {
410 if depth == 3
411 && let (Some(element), Some(key)) = (current.as_mut(), pending_key.take()) {
412 let text = e.unescape()?.trim().to_string();
413 if !text.is_empty() {
414 element.add_field(key, FieldValue::Text(text));
415 if let Some(lang) = pending_lang.take() {
416 element.add_lang(key, &lang);
417 }
418 }
419 }
420 }
421
422 Ok(Event::End(_)) => {
423 if depth == 2 {
424 pending_key = None;
425 pending_lang = None;
426 if let Some(element) = current.take()
427 && !element.mrid().is_empty() {
428 index(&mut ds.by_type, element.type_name(), element.mrid().to_string());
429 ds.entries.insert(element.mrid().to_string(), element);
430 }
431 if let Some(outer) = nested.take() {
432 scope = outer;
433 }
434 }
435 depth = depth.saturating_sub(1);
436 }
437
438 Ok(Event::Eof) => break,
439 Err(e) => return Err(Box::new(e)),
440 _ => {}
441 }
442 buf.clear();
443 }
444
445 Ok(())
446}
447
448fn strip_fragment(s: &str) -> String {
453 if let Some(i) = s.rfind('#') {
454 s[i + 1..].to_string()
455 } else {
456 s.trim_start_matches('#').to_string()
457 }
458}
459
460
461fn field_attrs(
464 attrs: quick_xml::events::attributes::Attributes<'_>,
465) -> Result<(Option<String>, Option<String>), Box<dyn std::error::Error>> {
466 let (mut res, mut lang) = (None, None);
467 for attr in attrs.flatten() {
468 match attr.key.as_ref() {
469 b"rdf:resource" => res = Some(strip_fragment(std::str::from_utf8(&attr.value)?)),
470 b"xml:lang" => lang = Some(std::str::from_utf8(&attr.value)?.to_string()),
471 _ => {}
472 }
473 }
474 Ok((res, lang))
475}
476
477fn find_resource(
479 attrs: quick_xml::events::attributes::Attributes<'_>,
480) -> Result<Option<String>, Box<dyn std::error::Error>> {
481 for attr in attrs.flatten() {
482 let key = std::str::from_utf8(attr.key.as_ref())?;
483 if key == "rdf:resource" {
484 return Ok(Some(strip_fragment(std::str::from_utf8(&attr.value)?)));
485 }
486 }
487 Ok(None)
488}