use super::*;
#[cfg_attr(not(feature = "kaiv"), allow(dead_code))]
pub(super) fn node_to_json(adapter: &impl AstAdapter, node: NodeId) -> Value {
struct Frame {
node: NodeId,
children: Vec<NodeId>,
next: usize,
built: Vec<(String, Value)>,
}
let frame = |n: NodeId| Frame {
node: n,
children: adapter.children(n),
next: 0,
built: Vec::new(),
};
let mut stack = vec![frame(node)];
loop {
let top = stack.last_mut().expect("a frame");
if top.next < top.children.len() {
let c = top.children[top.next];
top.next += 1;
stack.push(frame(c));
continue;
}
let done = stack.pop().expect("a frame");
let value = node_value_from(adapter, done.node, done.built);
match stack.last_mut() {
Some(parent) => parent
.built
.push((adapter.name(done.node).unwrap_or_default(), value)),
None => return value,
}
}
}
pub(super) fn node_to_json_text(adapter: &impl AstAdapter, node: NodeId) -> String {
struct Frame {
node: NodeId,
children: Vec<NodeId>,
next: usize,
built: Vec<(String, String)>,
}
let frame = |n: NodeId| Frame {
node: n,
children: adapter.children(n),
next: 0,
built: Vec::new(),
};
let mut stack = vec![frame(node)];
loop {
let top = stack.last_mut().expect("a frame");
if top.next < top.children.len() {
let c = top.children[top.next];
top.next += 1;
stack.push(frame(c));
continue;
}
let done = stack.pop().expect("a frame");
let mut entries = done.built;
for (k, v) in adapter.properties(done.node) {
if !entries.iter().any(|(n, _)| *n == k) {
entries.push((k, v.to_json()));
}
}
let text = if entries.is_empty() {
adapter
.default_value(done.node)
.unwrap_or(Value::Null)
.to_json()
} else {
if entries.len() > 1 && entries.iter().all(|(n, _)| n.parse::<usize>().is_ok()) {
entries.sort_by_key(|(n, _)| n.parse::<usize>().unwrap_or(usize::MAX));
}
let is_array = entries
.iter()
.enumerate()
.all(|(i, (n, _))| n == &i.to_string());
if is_array {
let inner: Vec<&str> = entries.iter().map(|(_, t)| t.as_str()).collect();
format!("[{}]", inner.join(", "))
} else {
let inner: Vec<String> = entries
.iter()
.map(|(k, t)| format!("{}: {t}", Value::Str(k.clone()).to_json()))
.collect();
format!("{{{}}}", inner.join(", "))
}
};
match stack.last_mut() {
Some(parent) => parent
.built
.push((adapter.name(done.node).unwrap_or_default(), text)),
None => return text,
}
}
}
#[cfg_attr(not(feature = "kaiv"), allow(dead_code))]
pub(super) fn node_value_from(
adapter: &impl AstAdapter,
node: NodeId,
mut entries: Vec<(String, Value)>,
) -> Value {
for (k, v) in adapter.properties(node) {
if !entries.iter().any(|(n, _)| *n == k) {
entries.push((k, v));
}
}
if entries.is_empty() {
return adapter.default_value(node).unwrap_or(Value::Null);
}
if entries.len() > 1 && entries.iter().all(|(n, _)| n.parse::<usize>().is_ok()) {
entries.sort_by_key(|(n, _)| n.parse::<usize>().unwrap_or(usize::MAX));
}
let is_array = entries
.iter()
.enumerate()
.all(|(i, (n, _))| n == &i.to_string());
if is_array {
Value::list(entries.into_iter().map(|(_, v)| v).collect())
} else {
Value::Record(entries)
}
}
pub(super) fn value_to_xml(v: &Value, tag: &str) -> String {
match v {
Value::Record(fields) => {
let mut attrs = String::new();
let mut body = String::new();
for (k, val) in fields {
if let Some(a) = k.strip_prefix('@') {
attrs.push_str(&format!(" {a}=\"{}\"", xml_escape(&val.to_string())));
} else if k == "#text" {
body.push_str(&xml_escape(&val.to_string()));
} else if let Value::List(items) = val {
for it in items.iter() {
body.push_str(&value_to_xml(it, &xml_tag(k)));
}
} else {
body.push_str(&value_to_xml(val, &xml_tag(k)));
}
}
format!("<{tag}{attrs}>{body}</{tag}>")
}
Value::List(items) => items.iter().map(|it| value_to_xml(it, tag)).collect(),
other => format!("<{tag}>{}</{tag}>", xml_escape(&other.to_string())),
}
}
pub(super) fn node_to_xml(adapter: &impl AstAdapter, node: NodeId) -> String {
match adapter.name(node) {
Some(tag) => node_to_xml_named(adapter, node, &xml_tag(&tag)),
None => adapter
.children(node)
.iter()
.map(|&c| {
let t = adapter
.name(c)
.map(|n| xml_tag(&n))
.unwrap_or_else(|| "item".into());
node_to_xml_named(adapter, c, &t)
})
.collect(),
}
}
pub(super) fn node_to_xml_named(adapter: &impl AstAdapter, node: NodeId, tag: &str) -> String {
struct Frame {
node: NodeId,
tag: String,
children: Vec<NodeId>,
names: Vec<Option<String>>,
next: usize,
inner: String,
}
let frame = |n: NodeId, tag: String| {
let children = adapter.children(n);
let names: Vec<Option<String>> = children.iter().map(|&c| adapter.name(c)).collect();
Frame {
node: n,
tag,
children,
names,
next: 0,
inner: String::new(),
}
};
let mut stack = vec![frame(node, tag.to_string())];
loop {
let top = stack.last_mut().expect("a frame");
if top.next < top.children.len() {
let c = top.children[top.next];
let t = top.names[top.next]
.as_deref()
.map(xml_tag)
.unwrap_or_else(|| "item".into());
top.next += 1;
stack.push(frame(c, t));
continue;
}
let done = stack.pop().expect("a frame");
let props: Vec<(String, Value)> = adapter
.properties(done.node)
.into_iter()
.filter(|(k, _)| !done.names.iter().any(|n| n.as_deref() == Some(k.as_str())))
.collect();
let element = if done.children.is_empty() && props.is_empty() {
let body = adapter
.default_value(done.node)
.map(|v| xml_escape(&v.to_string()))
.unwrap_or_default();
format!("<{0}>{body}</{0}>", done.tag)
} else {
let mut inner = done.inner;
for (k, v) in &props {
inner.push_str(&value_to_xml(v, &xml_tag(k)));
}
format!("<{0}>{inner}</{0}>", done.tag)
};
match stack.last_mut() {
Some(parent) => parent.inner.push_str(&element),
None => return element,
}
}
}
pub(super) fn xml_tag(name: &str) -> String {
let mut chars = name.chars();
let ok = matches!(chars.next(), Some(c) if c.is_alphabetic() || c == '_')
&& chars.all(|c| c.is_alphanumeric() || matches!(c, '_' | '-' | '.' | ':'));
if ok {
name.to_string()
} else {
"item".to_string()
}
}
pub(super) fn xml_escape(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}