use std::sync::Arc;
use indexmap::IndexMap;
use serde_json::Value;
use crate::model::IfcxNode;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct FlatNode {
pub children: IndexMap<String, Option<String>>,
pub inherits: IndexMap<String, String>,
pub attributes: IndexMap<String, Arc<Value>>,
}
impl FlatNode {
pub fn merge(&mut self, node: &IfcxNode) {
if let Some(children) = &node.children {
for (name, child) in children {
self.children.insert(name.clone(), child.clone());
}
}
if let Some(inherits) = &node.inherits {
for (name, inherit) in inherits {
match inherit {
None => {
self.inherits.shift_remove(name);
}
Some(path) => {
self.inherits.insert(name.clone(), path.clone());
}
}
}
}
if let Some(attributes) = &node.attributes {
for (name, value) in attributes {
self.attributes
.insert(name.clone(), Arc::new(value.clone()));
}
}
}
pub fn merge_owned(&mut self, node: IfcxNode) {
self.merge_parts(node.children, node.inherits, node.attributes);
}
fn merge_parts(
&mut self,
children: Option<IndexMap<String, Option<String>>>,
inherits: Option<IndexMap<String, Option<String>>>,
attributes: Option<IndexMap<String, Value>>,
) {
if let Some(children) = children {
self.children.extend(children);
}
for (name, inherit) in inherits.into_iter().flatten() {
match inherit {
None => {
self.inherits.shift_remove(&name);
}
Some(path) => {
self.inherits.insert(name, path);
}
}
}
if let Some(attributes) = attributes {
self.attributes.extend(
attributes
.into_iter()
.map(|(name, value)| (name, Arc::new(value))),
);
}
}
}
pub fn flatten<'a, I>(nodes: I) -> IndexMap<String, FlatNode>
where
I: IntoIterator<Item = &'a IfcxNode>,
{
let mut flat: IndexMap<String, FlatNode> = IndexMap::new();
for node in nodes {
match flat.get_mut(node.path.as_str()) {
Some(existing) => existing.merge(node),
None => {
let mut merged = FlatNode::default();
merged.merge(node);
flat.insert(node.path.clone(), merged);
}
}
}
flat
}
pub fn flatten_owned<I>(nodes: I) -> IndexMap<String, FlatNode>
where
I: IntoIterator<Item = IfcxNode>,
{
let mut flat: IndexMap<String, FlatNode> = IndexMap::new();
for node in nodes {
let IfcxNode {
path,
children,
inherits,
attributes,
..
} = node;
flat.entry(path)
.or_default()
.merge_parts(children, inherits, attributes);
}
flat
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn node(value: Value) -> IfcxNode {
serde_json::from_value(value).unwrap()
}
fn keys<V>(map: &IndexMap<String, V>) -> Vec<&str> {
map.keys().map(String::as_str).collect()
}
#[test]
fn later_entries_replace_in_place() {
let nodes = [
node(json!({"path": "a", "children": {"x": "1", "y": "2"},
"attributes": {"p": 1, "q": 2}})),
node(json!({"path": "a", "children": {"x": "3"}, "attributes": {"p": 4}})),
];
let flat = flatten(&nodes);
assert_eq!(flatten_owned(nodes.clone()), flat);
let a = &flat["a"];
assert_eq!(keys(&a.children), ["x", "y"]);
assert_eq!(a.children["x"].as_deref(), Some("3"));
assert_eq!(keys(&a.attributes), ["p", "q"]);
assert_eq!(*a.attributes["p"], json!(4));
}
#[test]
fn null_inherit_removes_and_readd_moves_to_end() {
let nodes = [
node(json!({"path": "a", "inherits": {"t": "T", "u": "U"}})),
node(json!({"path": "a", "inherits": {"t": null}})),
node(json!({"path": "a", "inherits": {"t": "T2"}})),
];
let flat = flatten(&nodes);
assert_eq!(flatten_owned(nodes.clone()), flat);
assert_eq!(keys(&flat["a"].inherits), ["u", "t"]);
assert_eq!(flat["a"].inherits["t"], "T2");
}
#[test]
fn null_child_and_null_attribute_are_kept() {
let nodes = [
node(json!({"path": "a", "children": {"x": "1"}, "attributes": {"p": 1}})),
node(json!({"path": "a", "children": {"x": null}, "attributes": {"p": null}})),
];
let flat = flatten(&nodes);
assert_eq!(flatten_owned(nodes.clone()), flat);
assert_eq!(flat["a"].children["x"], None);
assert_eq!(*flat["a"].attributes["p"], Value::Null);
}
#[test]
fn paths_keep_first_appearance_order() {
let nodes = [
node(json!({"path": "b"})),
node(json!({"path": "a/x"})),
node(json!({"path": "a"})),
node(json!({"path": "b", "attributes": {"p": 1}})),
];
let flat = flatten(&nodes);
assert_eq!(flatten_owned(nodes.clone()), flat);
assert_eq!(keys(&flat), ["b", "a/x", "a"]);
assert_eq!(flat["a"], FlatNode::default());
}
}