use alloc::string::String;
use alloc::vec::Vec;
use core::fmt::Write as _;
use crate::node::NodeKind;
use crate::tree::{Dom, NodeRef};
impl Dom {
pub fn to_json(&self) -> String {
let mut out = String::with_capacity(self.len() * 24 + 32);
out.push_str("{\"type\":\"document\",\"errors\":");
let _ = write!(out, "{}", self.errors().len());
out.push_str(",\"children\":");
write_forest(&mut out, self);
out.push('}');
out
}
}
impl<'a> NodeRef<'a> {
pub fn to_json(&self) -> String {
let mut out = String::new();
let mut stack: Vec<Step<'a>> = Vec::new();
stack.push(Step::Open(*self));
process(&mut out, &mut stack);
out
}
}
enum Step<'a> {
Open(NodeRef<'a>),
Lit(&'static str),
}
fn write_forest(out: &mut String, dom: &Dom) {
out.push('[');
let mut stack: Vec<Step<'_>> = Vec::new();
for r in dom.roots().collect::<Vec<_>>().into_iter().rev() {
stack.push(Step::Open(r));
}
process(out, &mut stack);
out.push(']');
}
fn process(out: &mut String, stack: &mut Vec<Step<'_>>) {
while let Some(step) = stack.pop() {
match step {
Step::Lit(s) => out.push_str(s),
Step::Open(node) => write_open(out, node, stack),
}
}
}
fn write_open<'a>(out: &mut String, node: NodeRef<'a>, stack: &mut Vec<Step<'a>>) {
maybe_comma(out);
match node.kind() {
NodeKind::Element { tag } => {
out.push_str("{\"type\":\"element\",\"tag\":");
write_str(out, tag);
out.push_str(",\"attrs\":{");
let mut first = true;
for (k, v) in node.attributes() {
if !first {
out.push(',');
}
first = false;
write_str(out, k);
out.push(':');
write_str(out, v);
}
out.push_str("},\"children\":[");
stack.push(Step::Lit("]}"));
let mut child = node.last_child();
while let Some(c) = child {
stack.push(Step::Open(c));
child = c.prev_sibling();
}
}
NodeKind::Text(t) => {
out.push_str("{\"type\":\"text\",\"value\":");
write_str(out, t);
out.push('}');
}
NodeKind::Comment(c) => {
out.push_str("{\"type\":\"comment\",\"value\":");
write_str(out, c);
out.push('}');
}
NodeKind::Doctype(d) => {
out.push_str("{\"type\":\"doctype\",\"value\":");
write_str(out, d);
out.push('}');
}
}
}
fn maybe_comma(out: &mut String) {
if !out.is_empty() && out.as_bytes().last() != Some(&b'[') {
out.push(',');
}
}
fn write_str(out: &mut String, s: &str) {
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{08}' => out.push_str("\\b"),
'\u{0c}' => out.push_str("\\f"),
c if (c as u32) < 0x20 => {
out.push_str("\\u");
push_hex4(out, c as u32);
}
c => out.push(c),
}
}
out.push('"');
}
fn push_hex4(out: &mut String, v: u32) {
const HEX: [u8; 16] = *b"0123456789abcdef";
for shift in [12u32, 8, 4, 0] {
let nib = ((v >> shift) & 0xf) as usize;
out.push(HEX[nib] as char);
}
}