1use std::collections::HashMap;
2use std::path::Path;
3
4use quick_xml::events::Event;
5use quick_xml::Reader;
6
7use cimstructs::base::{CimElement, FieldValue, RdfBlock};
8use cimstructs::registry::{self, ParseFn};
9
10pub struct CimEntry {
11 pub element: Box<dyn CimElement>,
12 pub block: RdfBlock,
13}
14
15pub struct CimDataset {
16 pub entries: HashMap<String, CimEntry>,
17 pub by_type: HashMap<String, Vec<String>>,
19}
20
21impl Default for CimDataset {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27impl CimDataset {
28 pub fn new() -> Self {
29 Self {
30 entries: HashMap::new(),
31 by_type: HashMap::new(),
32 }
33 }
34
35 pub fn decode_str(content: &str) -> Result<Self, Box<dyn std::error::Error>> {
37 let mut ds = Self::new();
38 let reg = registry::registry();
39 parse_rdf(content, ®, &mut ds)?;
40 Ok(ds)
41 }
42
43 pub fn decode_file(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
45 Self::decode_str(&std::fs::read_to_string(path)?)
46 }
47
48 pub fn decode_files(paths: &[&Path]) -> Result<Self, Box<dyn std::error::Error>> {
50 let mut combined = Self::new();
51 for path in paths {
52 let file_ds = Self::decode_file(path)?;
53 combined.merge(file_ds);
54 }
55 Ok(combined)
56 }
57
58 pub fn decode_files_parallel_separate(paths: &[&Path]) -> Result<Vec<Self>, Box<dyn std::error::Error>> {
62 if paths.len() <= 1 {
63 return paths.iter().map(|p| Self::decode_file(p)).collect();
64 }
65 let results: Vec<Result<Self, String>> = std::thread::scope(|s| {
66 paths
67 .iter()
68 .map(|p| s.spawn(|| Self::decode_file(p).map_err(|e| e.to_string())))
69 .collect::<Vec<_>>()
70 .into_iter()
71 .map(|h| h.join().expect("decode thread panicked"))
72 .collect()
73 });
74 results
75 .into_iter()
76 .collect::<Result<_, String>>()
77 .map_err(|e| e.into())
78 }
79
80 pub fn decode_files_parallel(paths: &[&Path]) -> Result<Self, Box<dyn std::error::Error>> {
82 let datasets = Self::decode_files_parallel_separate(paths)?;
83 Ok(datasets
84 .into_iter()
85 .reduce(|mut a, b| {
86 a.merge(b);
87 a
88 })
89 .unwrap_or_default())
90 }
91
92 pub fn merge(&mut self, other: CimDataset) {
96 let reg = registry::registry();
97 for (mrid, incoming) in other.entries {
98 if let Some(existing) = self.entries.get_mut(&mrid) {
99 existing.block.merge_from(&incoming.block);
100 let type_name = existing.block.type_name.clone();
101 if let Some(f) = reg.get(type_name.as_str()) {
102 existing.element = f(&existing.block);
103 }
104 } else {
105 let type_name = incoming.element.type_name().to_string();
106 self.by_type.entry(type_name).or_default().push(mrid.clone());
107 self.entries.insert(mrid, incoming);
108 }
109 }
110 }
111
112 pub fn drop_blocks(&mut self) {
114 for entry in self.entries.values_mut() {
115 entry.block = RdfBlock::default();
116 }
117 }
118
119 pub fn set(&mut self, mrid: String, element: Box<dyn CimElement>) {
122 let new_type = element.type_name().to_string();
123 if let Some(old) = self.entries.get(&mrid) {
124 let old_type = old.element.type_name();
125 if old_type != new_type {
126 if let Some(v) = self.by_type.get_mut(old_type) {
127 v.retain(|m| m != &mrid);
128 }
129 }
130 }
131 let already_indexed = self
132 .by_type
133 .get(&new_type)
134 .is_some_and(|v| v.contains(&mrid));
135 if !already_indexed {
136 self.by_type.entry(new_type).or_default().push(mrid.clone());
137 }
138 let block = element.to_block();
139 self.entries.insert(mrid, CimEntry { element, block });
140 }
141
142 pub fn remove(&mut self, mrid: &str) -> Option<CimEntry> {
145 let removed = self.entries.remove(mrid)?;
146 if let Some(v) = self.by_type.get_mut(removed.element.type_name()) {
147 v.retain(|m| m != mrid);
148 }
149 Some(removed)
150 }
151}
152
153fn parse_rdf(
157 content: &str,
158 reg: &HashMap<&'static str, ParseFn>,
159 ds: &mut CimDataset,
160) -> Result<(), Box<dyn std::error::Error>> {
161 let mut reader = Reader::from_str(content);
162 let mut buf = Vec::new();
163
164 let mut depth: u32 = 0;
165 let mut current: Option<RdfBlock> = None;
166 let mut pending_key: Option<String> = None;
167
168 loop {
169 match reader.read_event_into(&mut buf) {
170 Ok(Event::Start(ref e)) => {
171 depth += 1;
172 let local = local_name(e.name().as_ref())?;
173
174 match depth {
175 2 => {
176 let mrid = extract_about(e.attributes())?;
177 current = Some(RdfBlock {
178 type_name: local,
179 mrid,
180 fields: HashMap::new(),
181 duplicate_fields: std::collections::HashSet::new(),
182 });
183 }
184 3 => {
185 if let Some(ref mut block) = current {
186 if let Some(res) = find_resource(e.attributes())? {
187 add_field(block, &local, FieldValue::Resource(res));
188 } else {
189 pending_key = Some(local);
190 }
191 }
192 }
193 _ => {}
194 }
195 }
196
197 Ok(Event::Empty(ref e)) => {
198 let local = local_name(e.name().as_ref())?;
199 match depth {
200 1 => {
201 let mrid = extract_about(e.attributes())?;
203 if !mrid.is_empty() {
204 if let Some(f) = reg.get(local.as_str()) {
205 let block = RdfBlock { type_name: local, mrid: mrid.clone(), fields: HashMap::new(), duplicate_fields: std::collections::HashSet::new() };
206 let element = f(&block);
207 let type_name = element.type_name().to_string();
208 ds.by_type.entry(type_name).or_default().push(mrid.clone());
209 ds.entries.insert(mrid, CimEntry { element, block });
210 }
211 }
212 }
213 2 => {
214 if let Some(ref mut block) = current {
216 if let Some(res) = find_resource(e.attributes())? {
217 add_field(block, &local, FieldValue::Resource(res));
218 }
219 }
220 }
221 _ => {}
222 }
223 }
224
225 Ok(Event::Text(ref e)) => {
226 if depth == 3 {
227 if let (Some(block), Some(key)) =
228 (&mut current, pending_key.take())
229 {
230 let text = e.unescape()?.trim().to_string();
231 if !text.is_empty() {
232 add_field(block, &key, FieldValue::Text(text));
233 }
234 }
235 }
236 }
237
238 Ok(Event::End(_)) => {
239 if depth == 2 {
240 pending_key = None;
241 if let Some(block) = current.take() {
242 if !block.mrid.is_empty() {
243 if let Some(f) = reg.get(block.type_name.as_str()) {
244 let element = f(&block);
245 let type_name = element.type_name().to_string();
246 ds.by_type.entry(type_name).or_default().push(block.mrid.clone());
247 ds.entries.insert(block.mrid.clone(), CimEntry { element, block });
248 }
249 }
250 }
251 }
252 depth = depth.saturating_sub(1);
253 }
254
255 Ok(Event::Eof) => break,
256 Err(e) => return Err(Box::new(e)),
257 _ => {}
258 }
259 buf.clear();
260 }
261
262 Ok(())
263}
264
265fn local_name(raw: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
269 let s = std::str::from_utf8(raw)?;
270 Ok(s.find(':').map(|i| &s[i + 1..]).unwrap_or(s).to_string())
271}
272
273fn strip_fragment(s: &str) -> String {
275 if let Some(i) = s.rfind('#') {
276 s[i + 1..].to_string()
277 } else {
278 s.trim_start_matches('#').to_string()
279 }
280}
281
282fn extract_about(
284 attrs: quick_xml::events::attributes::Attributes<'_>,
285) -> Result<String, Box<dyn std::error::Error>> {
286 for attr in attrs.flatten() {
287 let key = std::str::from_utf8(attr.key.as_ref())?;
288 if key == "rdf:about" || key == "rdf:ID" {
289 return Ok(strip_fragment(std::str::from_utf8(&attr.value)?));
290 }
291 }
292 Ok(String::new())
293}
294
295fn find_resource(
297 attrs: quick_xml::events::attributes::Attributes<'_>,
298) -> Result<Option<String>, Box<dyn std::error::Error>> {
299 for attr in attrs.flatten() {
300 let key = std::str::from_utf8(attr.key.as_ref())?;
301 if key == "rdf:resource" {
302 return Ok(Some(strip_fragment(std::str::from_utf8(&attr.value)?)));
303 }
304 }
305 Ok(None)
306}
307
308fn add_field(block: &mut RdfBlock, key: &str, val: FieldValue) {
313 match &val {
314 FieldValue::Resource(new_ref) => match block.fields.get_mut(key) {
315 Some(FieldValue::ResourceList(list)) => {
316 list.push(new_ref.clone());
317 block.duplicate_fields.insert(key.to_string());
318 return;
319 }
320 Some(existing @ FieldValue::Resource(_)) => {
321 let old = match existing {
322 FieldValue::Resource(s) => s.clone(),
323 _ => unreachable!(),
324 };
325 *existing = FieldValue::ResourceList(vec![old, new_ref.clone()]);
326 block.duplicate_fields.insert(key.to_string());
327 return;
328 }
329 _ => {}
330 },
331 FieldValue::Text(new_text) => match block.fields.get_mut(key) {
332 Some(FieldValue::TextList(list)) => {
333 list.push(new_text.clone());
334 block.duplicate_fields.insert(key.to_string());
335 return;
336 }
337 Some(existing @ FieldValue::Text(_)) => {
338 let old = match existing {
339 FieldValue::Text(s) => s.clone(),
340 _ => unreachable!(),
341 };
342 *existing = FieldValue::TextList(vec![old, new_text.clone()]);
343 block.duplicate_fields.insert(key.to_string());
344 return;
345 }
346 _ => {}
347 },
348 _ => {
349 if block.fields.contains_key(key) {
350 block.duplicate_fields.insert(key.to_string());
351 }
352 }
353 }
354 block.fields.insert(key.to_string(), val);
355}