domtree/serialize/
json.rs1use alloc::string::String;
8use alloc::vec::Vec;
9use core::fmt::Write as _;
10
11use crate::node::NodeKind;
12use crate::tree::{Dom, NodeRef};
13
14impl Dom {
15 pub fn to_json(&self) -> String {
30 let mut out = String::with_capacity(self.len() * 24 + 32);
31 out.push_str("{\"type\":\"document\",\"errors\":");
32 let _ = write!(out, "{}", self.errors().len());
33 out.push_str(",\"children\":");
34 write_forest(&mut out, self);
35 out.push('}');
36 out
37 }
38}
39
40impl<'a> NodeRef<'a> {
41 pub fn to_json(&self) -> String {
50 let mut out = String::new();
51 let mut stack: Vec<Step<'a>> = Vec::new();
52 stack.push(Step::Open(*self));
53 process(&mut out, &mut stack);
54 out
55 }
56}
57
58enum Step<'a> {
59 Open(NodeRef<'a>),
60 Lit(&'static str),
61}
62
63fn write_forest(out: &mut String, dom: &Dom) {
64 out.push('[');
65 let mut stack: Vec<Step<'_>> = Vec::new();
66 for r in dom.roots().collect::<Vec<_>>().into_iter().rev() {
68 stack.push(Step::Open(r));
69 }
70 process(out, &mut stack);
71 out.push(']');
72}
73
74fn process(out: &mut String, stack: &mut Vec<Step<'_>>) {
75 while let Some(step) = stack.pop() {
76 match step {
77 Step::Lit(s) => out.push_str(s),
78 Step::Open(node) => write_open(out, node, stack),
79 }
80 }
81}
82
83fn write_open<'a>(out: &mut String, node: NodeRef<'a>, stack: &mut Vec<Step<'a>>) {
84 maybe_comma(out);
85 match node.kind() {
86 NodeKind::Element { tag } => {
87 out.push_str("{\"type\":\"element\",\"tag\":");
88 write_str(out, tag);
89 out.push_str(",\"attrs\":{");
90 let mut first = true;
91 for (k, v) in node.attributes() {
92 if !first {
93 out.push(',');
94 }
95 first = false;
96 write_str(out, k);
97 out.push(':');
98 write_str(out, v);
99 }
100 out.push_str("},\"children\":[");
101 stack.push(Step::Lit("]}"));
103 let mut child = node.last_child();
106 while let Some(c) = child {
107 stack.push(Step::Open(c));
108 child = c.prev_sibling();
109 }
110 }
111 NodeKind::Text(t) => {
112 out.push_str("{\"type\":\"text\",\"value\":");
113 write_str(out, t);
114 out.push('}');
115 }
116 NodeKind::Comment(c) => {
117 out.push_str("{\"type\":\"comment\",\"value\":");
118 write_str(out, c);
119 out.push('}');
120 }
121 NodeKind::Doctype(d) => {
122 out.push_str("{\"type\":\"doctype\",\"value\":");
123 write_str(out, d);
124 out.push('}');
125 }
126 }
127}
128
129fn maybe_comma(out: &mut String) {
133 if !out.is_empty() && out.as_bytes().last() != Some(&b'[') {
134 out.push(',');
135 }
136}
137
138fn write_str(out: &mut String, s: &str) {
140 out.push('"');
141 for c in s.chars() {
142 match c {
143 '"' => out.push_str("\\\""),
144 '\\' => out.push_str("\\\\"),
145 '\n' => out.push_str("\\n"),
146 '\r' => out.push_str("\\r"),
147 '\t' => out.push_str("\\t"),
148 '\u{08}' => out.push_str("\\b"),
149 '\u{0c}' => out.push_str("\\f"),
150 c if (c as u32) < 0x20 => {
151 out.push_str("\\u");
152 push_hex4(out, c as u32);
153 }
154 c => out.push(c),
155 }
156 }
157 out.push('"');
158}
159
160fn push_hex4(out: &mut String, v: u32) {
161 const HEX: [u8; 16] = *b"0123456789abcdef";
162 for shift in [12u32, 8, 4, 0] {
163 let nib = ((v >> shift) & 0xf) as usize;
164 out.push(HEX[nib] as char);
165 }
166}