use crate::marshal::{register_typed_fn_1, register_typed_fn_1_full};
use crate::module_exports::{ModuleExports, ModuleParam};
use crate::type_schema::register_predeclared_any_schema;
use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use quick_xml::{Reader, Writer};
use shape_value::heap_value::{HashMapData, HeapValue, TypedObjectStorage};
use shape_value::v2::typed_array::TypedArray;
use shape_value::{HeapKind, NativeKind, ValueSlot};
use std::io::Cursor;
use std::sync::Arc;
const XML_NODE_FIELDS: &[&str] = &["name", "attributes", "children", "text"];
struct ElementData {
name: String,
attributes: Vec<(String, String)>,
children: Vec<ElementData>,
text: Option<String>,
}
impl ElementData {
fn into_typed_object_arc(self) -> Arc<HeapValue> {
let mut attrs_data: HashMapData<*const shape_value::v2::string_obj::StringObj> =
HashMapData::new();
for (k, v) in &self.attributes {
let v_obj = shape_value::v2::string_obj::StringObj::new(v.as_str())
as *const shape_value::v2::string_obj::StringObj;
unsafe { attrs_data.insert(k.as_str(), v_obj) };
}
let attrs_data: shape_value::heap_value::HashMapKindedRef =
shape_value::heap_value::HashMapKindedRef::String(Arc::new(attrs_data));
let child_ptrs: Vec<*const TypedObjectStorage> = self
.children
.into_iter()
.map(|c| {
let child_hv = c.into_typed_object_arc();
let to_ptr = match &*child_hv {
HeapValue::TypedObject(s) => s.clone(),
_ => unreachable!(
"into_typed_object_arc must return HeapValue::TypedObject"
),
};
to_ptr.into_raw()
})
.collect();
let children_arr: *mut TypedArray<*const TypedObjectStorage> =
TypedArray::<*const TypedObjectStorage>::from_slice(&child_ptrs);
let schema_id = ensure_xml_node_schema();
let name_arc = Arc::new(self.name);
let attrs_arc = Arc::new(attrs_data);
let text_arc = Arc::new(self.text.unwrap_or_default());
let slots: Box<[ValueSlot]> = Box::new([
ValueSlot::from_string_arc(name_arc),
ValueSlot::from_hashmap(attrs_arc),
ValueSlot::from_u64(children_arr as u64),
ValueSlot::from_string_arc(text_arc),
]);
let field_kinds: Arc<[NativeKind]> = Arc::from(
vec![
NativeKind::String,
NativeKind::Ptr(HeapKind::HashMap),
NativeKind::Ptr(HeapKind::TypedArray),
NativeKind::String,
]
.into_boxed_slice(),
);
let heap_mask: u64 = 0b1111; let storage = TypedObjectStorage::_new(
schema_id as u64,
slots,
heap_mask,
field_kinds,
);
Arc::new(HeapValue::TypedObject(
shape_value::heap_value::TypedObjectPtr::new(storage),
))
}
fn into_root_pairs(self) -> Vec<(String, ConcreteReturn)> {
let attrs_pairs: Vec<(String, String)> = self.attributes;
let children_arc: Vec<Arc<HeapValue>> = self
.children
.into_iter()
.map(ElementData::into_typed_object_arc)
.collect();
let mut pairs = vec![
("name".to_string(), ConcreteReturn::String(self.name)),
(
"attributes".to_string(),
ConcreteReturn::HashMapStringString(attrs_pairs),
),
(
"children".to_string(),
ConcreteReturn::ArrayHeapValue(children_arc),
),
];
if let Some(text) = self.text {
pairs.push(("text".to_string(), ConcreteReturn::String(text)));
}
pairs
}
}
fn ensure_xml_node_schema() -> u32 {
let owned: Vec<String> = XML_NODE_FIELDS.iter().map(|s| s.to_string()).collect();
register_predeclared_any_schema(&owned)
}
fn parse_element(
reader: &mut Reader<&[u8]>,
start: &BytesStart,
) -> Result<ElementData, String> {
let name = std::str::from_utf8(start.name().as_ref())
.map_err(|e| format!("Invalid UTF-8 in element name: {}", e))?
.to_string();
let mut attributes = Vec::new();
for attr in start.attributes() {
let attr = attr.map_err(|e| format!("Invalid attribute: {}", e))?;
let key = std::str::from_utf8(attr.key.as_ref())
.map_err(|e| format!("Invalid UTF-8 in attribute key: {}", e))?
.to_string();
let value = attr
.unescape_value()
.map_err(|e| format!("Invalid attribute value: {}", e))?
.to_string();
attributes.push((key, value));
}
let mut children = Vec::new();
let mut text_parts = Vec::new();
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) => {
let child = parse_element(reader, e)?;
children.push(child);
}
Ok(Event::Empty(ref e)) => {
let child = parse_empty_element(e)?;
children.push(child);
}
Ok(Event::Text(ref e)) => {
let t = e
.unescape()
.map_err(|err| format!("Error unescaping text: {}", err))?
.to_string();
let trimmed = t.trim().to_string();
if !trimmed.is_empty() {
text_parts.push(trimmed);
}
}
Ok(Event::CData(ref e)) => {
let t = std::str::from_utf8(e.as_ref())
.map_err(|err| format!("Invalid UTF-8 in CDATA: {}", err))?
.to_string();
if !t.trim().is_empty() {
text_parts.push(t);
}
}
Ok(Event::End(_)) => break,
Ok(Event::Eof) => {
return Err("Unexpected end of XML".to_string());
}
Ok(_) => {} Err(e) => return Err(format!("XML parse error: {}", e)),
}
buf.clear();
}
Ok(ElementData {
name,
attributes,
children,
text: if text_parts.is_empty() {
None
} else {
Some(text_parts.join(""))
},
})
}
fn parse_empty_element(start: &BytesStart) -> Result<ElementData, String> {
let name = std::str::from_utf8(start.name().as_ref())
.map_err(|e| format!("Invalid UTF-8 in element name: {}", e))?
.to_string();
let mut attributes = Vec::new();
for attr in start.attributes() {
let attr = attr.map_err(|e| format!("Invalid attribute: {}", e))?;
let key = std::str::from_utf8(attr.key.as_ref())
.map_err(|e| format!("Invalid UTF-8 in attribute key: {}", e))?
.to_string();
let value = attr
.unescape_value()
.map_err(|e| format!("Invalid attribute value: {}", e))?
.to_string();
attributes.push((key, value));
}
Ok(ElementData {
name,
attributes,
children: Vec::new(),
text: None,
})
}
fn write_node_pairs(
writer: &mut Writer<Cursor<Vec<u8>>>,
pairs: &[(Arc<String>, Arc<HeapValue>)],
) -> Result<(), String> {
let _ = (writer, pairs);
let _ = write_xml_element; Err(
"xml.stringify(): V3-S5 ckpt-5-prime²c SURFACE — top-level \
pair-list reader needs Vec<Arc<HeapValue>> rewire for the deleted \
outer-array-arm. Round 2 follow-up (pairs with per-element-kind \
constructor wave). ADR-006 §2.7.24 Q25.A SUPERSEDED."
.to_string(),
)
}
fn write_typed_object_node(
writer: &mut Writer<Cursor<Vec<u8>>>,
storage: &TypedObjectStorage,
) -> Result<(), String> {
if storage.slots.len() != XML_NODE_FIELDS.len() {
return Err(format!(
"xml.stringify(): child TypedObject has {} slots, expected {}",
storage.slots.len(),
XML_NODE_FIELDS.len()
));
}
let name_slot = &storage.slots[0];
let attrs_slot = &storage.slots[1];
let children_slot = &storage.slots[2];
let text_slot = &storage.slots[3];
let name: String = unsafe {
let bits = name_slot.raw();
if bits == 0 {
return Err("xml.stringify(): TypedObject name slot is null".to_string());
}
let arc_ptr = bits as *const String;
Arc::increment_strong_count(arc_ptr);
let arc = Arc::from_raw(arc_ptr);
let owned = (*arc).clone();
owned
};
let attrs_kref: Option<shape_value::heap_value::HashMapKindedRef> = unsafe {
let bits = attrs_slot.raw();
if bits == 0 {
None
} else {
let arc_ptr = bits as *const shape_value::heap_value::HashMapKindedRef;
Arc::increment_strong_count(arc_ptr);
let arc = Arc::from_raw(arc_ptr);
let cloned = (*arc).clone();
Some(cloned)
}
};
let children_ptr: *const TypedArray<*const TypedObjectStorage> = {
let bits = children_slot.raw();
if bits == 0 {
std::ptr::null()
} else {
bits as usize as *const TypedArray<*const TypedObjectStorage>
}
};
let text: Option<String> = unsafe {
let bits = text_slot.raw();
if bits == 0 {
None
} else {
let arc_ptr = bits as *const String;
Arc::increment_strong_count(arc_ptr);
let arc = Arc::from_raw(arc_ptr);
let owned = (*arc).clone();
if owned.is_empty() {
None
} else {
Some(owned)
}
}
};
write_xml_element(
writer,
Some(name),
attrs_kref.as_ref(),
children_ptr,
text.as_deref(),
)
}
fn write_xml_element(
writer: &mut Writer<Cursor<Vec<u8>>>,
name: Option<String>,
attrs: Option<&shape_value::heap_value::HashMapKindedRef>,
children: *const TypedArray<*const TypedObjectStorage>,
text: Option<&str>,
) -> Result<(), String> {
let name = name.ok_or_else(|| "xml.stringify(): node missing 'name' field".to_string())?;
let mut elem = BytesStart::new(name.clone());
if let Some(attrs) = attrs {
use shape_value::heap_value::HashMapKindedRef;
match attrs {
HashMapKindedRef::String(arc) => {
let n = arc.len();
for i in 0..n {
let key: String = unsafe {
let ptr = shape_value::v2::typed_array::TypedArray::get_unchecked(
arc.keys, i as u32,
);
shape_value::v2::string_obj::StringObj::as_str(ptr).to_owned()
};
let val: String = unsafe {
let v_ptr: *const shape_value::v2::string_obj::StringObj =
*(*arc.values).data.add(i);
shape_value::v2::string_obj::StringObj::as_str(v_ptr).to_owned()
};
elem.push_attribute((key.as_bytes(), val.as_bytes()));
}
}
other => {
return Err(format!(
"xml.stringify(): attributes HashMap must be HashMap<string, string>, \
got V={:?}",
other.values_kind()
));
}
}
}
let child_count: u32 = if children.is_null() {
0
} else {
unsafe { TypedArray::<*const TypedObjectStorage>::len(children) }
};
let has_children = child_count > 0;
let has_text = text.is_some();
if !has_children && !has_text {
writer
.write_event(Event::Empty(elem))
.map_err(|e| format!("xml.stringify() write error: {}", e))?;
} else {
writer
.write_event(Event::Start(elem.clone()))
.map_err(|e| format!("xml.stringify() write error: {}", e))?;
if let Some(text) = text {
writer
.write_event(Event::Text(BytesText::new(text)))
.map_err(|e| format!("xml.stringify() write error: {}", e))?;
}
if has_children {
let slice = unsafe {
TypedArray::<*const TypedObjectStorage>::as_slice(children)
};
for &child_ptr in slice.iter() {
unsafe {
write_typed_object_node(writer, &*child_ptr)?;
}
}
}
writer
.write_event(Event::End(BytesEnd::new(name)))
.map_err(|e| format!("xml.stringify() write error: {}", e))?;
}
Ok(())
}
pub fn create_xml_module() -> ModuleExports {
let mut module = ModuleExports::new("std::core::xml");
module.description = "XML parsing and serialization".to_string();
register_typed_fn_1::<_, Arc<String>>(
&mut module,
"parse",
"Parse an XML string into a Shape HashMap node",
"text",
"string",
ConcreteType::Result(Box::new(ConcreteType::HashMap)),
|text, _ctx| {
let mut reader = Reader::from_str(text.as_str());
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) => {
let inner = parse_element(&mut reader, e)?;
return Ok(TypedReturn::OkObjectPairs(inner.into_root_pairs()));
}
Ok(Event::Empty(ref e)) => {
let inner = parse_empty_element(e)?;
return Ok(TypedReturn::OkObjectPairs(inner.into_root_pairs()));
}
Ok(Event::Eof) => {
return Err("xml.parse(): no root element found".to_string());
}
Ok(_) => {} Err(e) => {
return Err(format!("xml.parse() failed: {}", e));
}
}
buf.clear();
}
},
);
register_typed_fn_1_full::<_, Vec<(Arc<String>, Arc<HeapValue>)>>(
&mut module,
"stringify",
"Serialize a Shape HashMap node to an XML string",
[ModuleParam {
name: "value".to_string(),
type_name: "HashMap<string, any>".to_string(),
required: true,
description:
"Node value to serialize (with name, attributes, children, text? fields)"
.to_string(),
..Default::default()
}],
ConcreteType::Result(Box::new(ConcreteType::String)),
|pairs: Vec<(Arc<String>, Arc<HeapValue>)>, _ctx| {
let mut writer = Writer::new(Cursor::new(Vec::new()));
write_node_pairs(&mut writer, &pairs)?;
let output = String::from_utf8(writer.into_inner().into_inner())
.map_err(|e| format!("xml.stringify(): invalid UTF-8 output: {}", e))?;
Ok(TypedReturn::Ok(ConcreteReturn::String(output)))
},
);
module
}