use super::config::FromXmlConfig;
use hedl_core::lex::{singularize_and_capitalize, Tensor};
use hedl_core::{Item, MatrixList, Node, Value};
use std::collections::BTreeMap;
pub(crate) fn items_to_matrix_list(
name: &str,
items: Vec<Item>,
config: &FromXmlConfig,
structs: &mut BTreeMap<String, Vec<String>>,
) -> Result<MatrixList, String> {
items_to_matrix_list_with_type(name, items, None, config, structs)
}
pub(crate) fn items_to_matrix_list_with_type(
name: &str,
items: Vec<Item>,
explicit_type: Option<String>,
_config: &FromXmlConfig,
structs: &mut BTreeMap<String, Vec<String>>,
) -> Result<MatrixList, String> {
let type_name = explicit_type.unwrap_or_else(|| singularize_and_capitalize(name));
let schema = infer_schema(&items)?;
structs.insert(type_name.clone(), schema.clone());
let mut rows = Vec::new();
for (idx, item) in items.into_iter().enumerate() {
let node = item_to_node(&type_name, &schema, item, idx)?;
rows.push(node);
}
Ok(MatrixList {
type_name,
schema,
rows,
count_hint: None,
})
}
pub(crate) fn infer_schema(items: &[Item]) -> Result<Vec<String>, String> {
if let Some(Item::Object(first_obj)) = items.first() {
let mut keys: Vec<_> = first_obj
.iter()
.filter(|(_, item)| matches!(item, Item::Scalar(_)))
.map(|(k, _)| k.clone())
.collect();
keys.sort();
if let Some(pos) = keys.iter().position(|k| k == "id") {
keys.remove(pos);
keys.insert(0, "id".to_string());
} else {
keys.insert(0, "id".to_string());
}
Ok(keys)
} else {
Ok(vec!["id".to_string(), "value".to_string()])
}
}
pub(crate) fn item_to_node(
type_name: &str,
schema: &[String],
item: Item,
idx: usize,
) -> Result<Node, String> {
match item {
Item::Object(obj) => {
let id = obj
.get(&schema[0])
.and_then(|i| i.as_scalar())
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.unwrap_or_else(|| format!("{}", idx));
let mut fields = Vec::new();
for col in schema {
let value = obj
.get(col)
.and_then(|i| i.as_scalar())
.cloned()
.unwrap_or(Value::Null);
fields.push(value);
}
let mut children: BTreeMap<String, Vec<Node>> = BTreeMap::new();
for child_item in obj.values() {
if let Item::List(child_list) = child_item {
children.insert(child_list.type_name.clone(), child_list.rows.clone());
}
}
Ok(Node {
type_name: type_name.to_string(),
id,
fields: fields.into(),
children: if children.is_empty() {
None
} else {
Some(Box::new(children))
},
child_count: 0,
})
}
Item::Scalar(value) => {
let id = format!("{}", idx);
Ok(Node {
type_name: type_name.to_string(),
id: id.clone(),
fields: vec![Value::String(id.into()), value].into(),
children: None,
child_count: 0,
})
}
Item::List(_) => Err("Cannot convert nested list to node".to_string()),
}
}
pub(crate) fn to_hedl_key(s: &str) -> String {
let mut result = String::new();
let mut prev_was_upper = false;
for (i, c) in s.chars().enumerate() {
if c.is_ascii_uppercase() {
if i > 0 && !prev_was_upper && !result.ends_with('_') {
result.push('_');
}
result.push(c.to_ascii_lowercase());
prev_was_upper = true;
} else if c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' {
result.push(c);
prev_was_upper = false;
} else {
if !result.is_empty() && !result.ends_with('_') {
result.push('_');
}
prev_was_upper = false;
}
}
while result.contains("__") {
result = result.replace("__", "_");
}
let result = result.trim_matches('_').to_string();
if result.is_empty() {
return "key".to_string();
}
if result.as_bytes()[0].is_ascii_digit() {
format!("_{}", result)
} else {
result
}
}
pub(crate) fn items_are_tensor_elements(items: &[Item]) -> bool {
items.iter().all(|item| {
match item {
Item::Scalar(Value::Int(_)) => true,
Item::Scalar(Value::Float(_)) => true,
Item::Scalar(Value::Tensor(_)) => true,
Item::Object(obj) if obj.len() == 1 => {
matches!(obj.get("item"), Some(Item::Scalar(Value::Tensor(_))))
}
_ => false,
}
})
}
pub(crate) fn items_are_list_elements(items: &[Item]) -> bool {
items.iter().all(|item| item.as_scalar().is_some())
}
pub(crate) fn items_to_tensor(items: &[Item]) -> Result<Tensor, String> {
let mut tensor_items = Vec::new();
for item in items {
let tensor = match item {
Item::Scalar(Value::Int(n)) => Tensor::Scalar(*n as f64),
Item::Scalar(Value::Float(f)) => Tensor::Scalar(*f),
Item::Scalar(Value::Tensor(t)) => (**t).clone(),
Item::Object(obj) if obj.len() == 1 => {
if let Some(Item::Scalar(Value::Tensor(t))) = obj.get("item") {
(**t).clone()
} else {
return Err("Cannot convert non-numeric item to tensor".to_string());
}
}
_ => return Err("Cannot convert non-numeric item to tensor".to_string()),
};
tensor_items.push(tensor);
}
Ok(Tensor::Array(tensor_items))
}
pub(crate) fn items_to_list(items: &[Item]) -> Result<Vec<Value>, String> {
items
.iter()
.map(|item| {
item.as_scalar()
.cloned()
.ok_or_else(|| "Cannot convert non-scalar item to list".to_string())
})
.collect()
}