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), Box<dyn std::error::Error>> {
242 let mut mrid = String::new();
243 let mut has_xmlns = false;
244 for attr in e.attributes().flatten() {
245 let key = attr.key.as_ref();
246 if key == b"rdf:about" || key == b"rdf:ID" {
247 mrid = strip_fragment(std::str::from_utf8(&attr.value)?);
248 } else if key.starts_with(b"xmlns") {
249 has_xmlns = true;
250 }
251 }
252 Ok((mrid, has_xmlns))
253}
254
255fn split_qname(raw: &[u8]) -> (Option<&[u8]>, &[u8]) {
257 match raw.iter().position(|b| *b == b':') {
258 Some(i) => (Some(&raw[..i]), &raw[i + 1..]),
259 None => (None, raw),
260 }
261}
262
263fn resolve(
273 scope: &Scope,
274 reg: &'static TypeRegistry,
275 raw: &[u8],
276) -> Result<Option<&'static ClassDef>, Box<dyn std::error::Error>> {
277 let (prefix, local_bytes) = split_qname(raw);
278 let local = std::str::from_utf8(local_bytes)?;
279 if let Some(class) = scope.table(prefix).and_then(|t| t.get(local)) {
280 return Ok(Some(*class));
281 }
282 Ok(reg.bare().get(local).copied())
283}
284
285fn parse_rdf(
287 content: &str,
288 reg: &'static TypeRegistry,
289 ds: &mut CimDataset,
290) -> Result<(), Box<dyn std::error::Error>> {
291 let mut reader = Reader::from_str(content);
292 let mut buf = Vec::new();
293
294 let mut depth: u32 = 0;
295 let mut scope = Scope::default();
296 let mut nested: Option<Scope> = None;
298 let mut current: Option<Element> = None;
301 let mut declared: Option<&'static FastMap<&'static str, &'static AttrDef>> = None;
303 let mut pending_key: Option<&'static str> = None;
304 let mut pending_lang: Option<String> = None;
306 let key_of = |declared: Option<&'static FastMap<&'static str, &'static AttrDef>>, local: &str| {
309 declared.and_then(|d| d.get(local)).map_or_else(|| intern(local), |a| a.id)
310 };
311
312 loop {
313 match reader.read_event_into(&mut buf) {
314 Ok(Event::Start(ref e)) => {
315 depth += 1;
316
317 match depth {
318 1 => scope.absorb(e, reg),
319 2 => {
320 let (mrid, has_xmlns) = scan_attrs(e)?;
325 if has_xmlns {
326 let mut inner = Scope {
327 prefixes: scope.prefixes.clone(),
328 default: scope.default,
329 };
330 inner.absorb(e, reg);
331 nested = Some(std::mem::replace(&mut scope, inner));
332 }
333
334 current = resolve(&scope, reg, e.name().as_ref())?.map(|class| Element::new(class, mrid));
335 declared = current.as_ref().and_then(|c| reg.declared(c.class()));
336 }
337 3 => {
338 if let Some(element) = current.as_mut() {
339 let name = e.name();
340 let (_, local_bytes) = split_qname(name.as_ref());
341 let local = key_of(declared, std::str::from_utf8(local_bytes)?);
342 let (res, lang) = field_attrs(e.attributes())?;
343 if let Some(res) = res {
344 element.add_field(local, FieldValue::Resource(res));
345 } else {
346 pending_key = Some(local);
347 pending_lang = lang;
348 }
349 }
350 }
351 _ => {}
352 }
353 }
354
355 Ok(Event::Empty(ref e)) => {
356 match depth {
357 1 => {
358 let (mrid, has_xmlns) = scan_attrs(e)?;
363 let local_scope = if has_xmlns {
364 let mut inner = Scope {
365 prefixes: scope.prefixes.clone(),
366 default: scope.default,
367 };
368 inner.absorb(e, reg);
369 Some(inner)
370 } else {
371 None
372 };
373 let active = local_scope.as_ref().unwrap_or(&scope);
374 if !mrid.is_empty()
375 && let Some(class) = resolve(active, reg, e.name().as_ref())? {
376 index(&mut ds.by_type, class.qualified, mrid.clone());
377 ds.entries.insert(mrid.clone(), Element::new(class, mrid));
378 }
379 }
380 2 => {
381 if let Some(element) = current.as_mut()
383 && let Some(res) = find_resource(e.attributes())? {
384 let name = e.name();
385 let (_, local_bytes) = split_qname(name.as_ref());
386 let local = key_of(declared, std::str::from_utf8(local_bytes)?);
387 element.add_field(local, FieldValue::Resource(res));
388 }
389 }
390 _ => {}
391 }
392 }
393
394 Ok(Event::Text(ref e)) => {
395 if depth == 3
396 && let (Some(element), Some(key)) = (current.as_mut(), pending_key.take()) {
397 let text = e.unescape()?.trim().to_string();
398 if !text.is_empty() {
399 element.add_field(key, FieldValue::Text(text));
400 if let Some(lang) = pending_lang.take() {
401 element.add_lang(key, &lang);
402 }
403 }
404 }
405 }
406
407 Ok(Event::End(_)) => {
408 if depth == 2 {
409 pending_key = None;
410 pending_lang = None;
411 if let Some(element) = current.take()
412 && !element.mrid().is_empty() {
413 index(&mut ds.by_type, element.type_name(), element.mrid().to_string());
414 ds.entries.insert(element.mrid().to_string(), element);
415 }
416 if let Some(outer) = nested.take() {
417 scope = outer;
418 }
419 }
420 depth = depth.saturating_sub(1);
421 }
422
423 Ok(Event::Eof) => break,
424 Err(e) => return Err(Box::new(e)),
425 _ => {}
426 }
427 buf.clear();
428 }
429
430 Ok(())
431}
432
433fn strip_fragment(s: &str) -> String {
438 if let Some(i) = s.rfind('#') {
439 s[i + 1..].to_string()
440 } else {
441 s.trim_start_matches('#').to_string()
442 }
443}
444
445
446fn field_attrs(
449 attrs: quick_xml::events::attributes::Attributes<'_>,
450) -> Result<(Option<String>, Option<String>), Box<dyn std::error::Error>> {
451 let (mut res, mut lang) = (None, None);
452 for attr in attrs.flatten() {
453 match attr.key.as_ref() {
454 b"rdf:resource" => res = Some(strip_fragment(std::str::from_utf8(&attr.value)?)),
455 b"xml:lang" => lang = Some(std::str::from_utf8(&attr.value)?.to_string()),
456 _ => {}
457 }
458 }
459 Ok((res, lang))
460}
461
462fn find_resource(
464 attrs: quick_xml::events::attributes::Attributes<'_>,
465) -> Result<Option<String>, Box<dyn std::error::Error>> {
466 for attr in attrs.flatten() {
467 let key = std::str::from_utf8(attr.key.as_ref())?;
468 if key == "rdf:resource" {
469 return Ok(Some(strip_fragment(std::str::from_utf8(&attr.value)?)));
470 }
471 }
472 Ok(None)
473}