1use satteri_arena::{Arena, ArenaBuilder, StringRef};
34use satteri_ast::commands::{CommandError, JsNode};
35use satteri_ast::hast::HastNodeType;
36use satteri_ast::mdast::codec::*;
37use satteri_ast::mdast::MdastNodeType;
38use satteri_ast::rebuild::Patch;
39use satteri_ast::shared::{
40 encode_js_jsx_attrs, PROP_BOOL_FALSE, PROP_BOOL_TRUE, PROP_INT, PROP_NULL, PROP_SPACE_SEP,
41 PROP_STRING,
42};
43
44const CMD_REMOVE: u8 = 0x01;
46const CMD_INSERT_BEFORE: u8 = 0x05;
47const CMD_INSERT_AFTER: u8 = 0x06;
48const CMD_PREPEND_CHILD: u8 = 0x07;
49const CMD_APPEND_CHILD: u8 = 0x08;
50const CMD_WRAP: u8 = 0x09;
51const CMD_REPLACE: u8 = 0x0B;
52const CMD_SET_PROPERTY: u8 = 0x0C;
53
54const PAYLOAD_RAW_MARKDOWN: u8 = 0x10;
55const PAYLOAD_RAW_HTML: u8 = 0x11;
56const PAYLOAD_SERDE_JSON: u8 = 0x12;
57
58const FIELD_DEPTH: u16 = 0x0001;
60const FIELD_URL: u16 = 0x0010;
61const FIELD_TITLE: u16 = 0x0011;
62const FIELD_LANG: u16 = 0x0020;
63const FIELD_META: u16 = 0x0021;
64const FIELD_VALUE: u16 = 0x0022;
65const FIELD_ALT: u16 = 0x0030;
66const FIELD_ORDERED: u16 = 0x0040;
67const FIELD_START: u16 = 0x0041;
68const FIELD_SPREAD: u16 = 0x0042;
69const FIELD_CHECKED: u16 = 0x0050;
70const FIELD_IDENTIFIER: u16 = 0x0060;
71const FIELD_LABEL: u16 = 0x0061;
72const FIELD_REFERENCE_TYPE: u16 = 0x0062;
73const FIELD_NAME: u16 = 0x0070;
74
75struct BufReader<'a> {
76 data: &'a [u8],
77 pos: usize,
78}
79
80impl<'a> BufReader<'a> {
81 fn new(data: &'a [u8]) -> Self {
82 Self { data, pos: 0 }
83 }
84
85 fn remaining(&self) -> usize {
86 self.data.len() - self.pos
87 }
88
89 fn read_u8(&mut self) -> Result<u8, CommandError> {
90 if self.remaining() < 1 {
91 return Err(CommandError::UnexpectedEof);
92 }
93 let v = self.data[self.pos];
94 self.pos += 1;
95 Ok(v)
96 }
97
98 fn read_u32(&mut self) -> Result<u32, CommandError> {
99 if self.remaining() < 4 {
100 return Err(CommandError::UnexpectedEof);
101 }
102 let v = u32::from_le_bytes([
103 self.data[self.pos],
104 self.data[self.pos + 1],
105 self.data[self.pos + 2],
106 self.data[self.pos + 3],
107 ]);
108 self.pos += 4;
109 Ok(v)
110 }
111
112 fn read_bytes(&mut self, len: usize) -> Result<&'a [u8], CommandError> {
113 if self.remaining() < len {
114 return Err(CommandError::UnexpectedEof);
115 }
116 let slice = &self.data[self.pos..self.pos + len];
117 self.pos += len;
118 Ok(slice)
119 }
120
121 fn read_str(&mut self, len: usize) -> Result<&'a str, CommandError> {
122 let bytes = self.read_bytes(len)?;
123 std::str::from_utf8(bytes).map_err(|_| CommandError::InvalidUtf8)
124 }
125}
126
127fn resolve_mdast_field(node_type: u8, name: &str) -> Option<u16> {
129 match (node_type, name) {
130 (2, "depth") => Some(FIELD_DEPTH),
131 (15, "url") | (16, "url") | (9, "url") => Some(FIELD_URL),
132 (15, "title") | (16, "title") | (9, "title") => Some(FIELD_TITLE),
133 (8, "lang") => Some(FIELD_LANG),
134 (8, "meta") | (27, "meta") => Some(FIELD_META),
135 (10 | 13 | 7 | 25 | 26 | 28, "value")
136 | (8, "value")
137 | (27, "value")
138 | (102..=104, "value") => Some(FIELD_VALUE),
139 (16, "alt") => Some(FIELD_ALT),
140 (5, "ordered") => Some(FIELD_ORDERED),
141 (5, "start") => Some(FIELD_START),
142 (5 | 6, "spread") => Some(FIELD_SPREAD),
143 (6, "checked") => Some(FIELD_CHECKED),
144 (9 | 17 | 18 | 19 | 20, "identifier") => Some(FIELD_IDENTIFIER),
145 (9 | 17 | 18 | 19 | 20, "label") => Some(FIELD_LABEL),
146 (17 | 18 | 20, "referenceType") => Some(FIELD_REFERENCE_TYPE),
147 (100 | 101, "name") => Some(FIELD_NAME),
148 _ => None,
149 }
150}
151
152fn apply_set_property(
157 arena: &mut Arena,
158 node_id: u32,
159 prop_name: &str,
160 value_type: u8,
161 value_str: &str,
162) -> Result<(), CommandError> {
163 if let Some(result) = apply_hast_set_property(arena, node_id, prop_name, value_type, value_str)
165 {
166 return result;
167 }
168
169 if prop_name == "data" {
171 if value_type == PROP_NULL {
172 arena.set_node_data(node_id, Vec::new());
173 } else {
174 arena.set_node_data(node_id, value_str.as_bytes().to_vec());
175 }
176 return Ok(());
177 }
178
179 let node_type = arena.get_node(node_id).node_type;
181 let field_id =
182 resolve_mdast_field(node_type, prop_name).ok_or(CommandError::UnknownField(0))?;
183
184 match value_type {
185 PROP_STRING | PROP_SPACE_SEP => {
186 let sref = arena.alloc_string(value_str);
187 set_string_ref(arena, node_id, field_id, sref)
188 }
189 PROP_BOOL_TRUE => apply_mdast_bool(arena, node_id, node_type, field_id, true),
190 PROP_BOOL_FALSE => apply_mdast_bool(arena, node_id, node_type, field_id, false),
191 PROP_INT => {
192 let value: i64 = value_str.parse().unwrap_or(0);
193 apply_mdast_int(arena, node_id, node_type, field_id, value)
194 }
195 PROP_NULL => apply_mdast_null(arena, node_id, node_type, field_id),
196 _ => Err(CommandError::UnknownCommand(value_type)),
197 }
198}
199
200fn apply_mdast_int(
201 arena: &mut Arena,
202 node_id: u32,
203 node_type: u8,
204 field_id: u16,
205 value: i64,
206) -> Result<(), CommandError> {
207 let data_offset = arena.get_node(node_id).data_offset as usize;
208 let data_len = arena.get_node(node_id).data_len as usize;
209 match (node_type, field_id) {
210 (2, FIELD_DEPTH) => {
211 if data_len >= 1 {
212 arena.type_data[data_offset] = value as u8;
213 }
214 }
215 (5, FIELD_START) => {
216 if data_len >= 4 {
217 arena.type_data[data_offset..data_offset + 4]
218 .copy_from_slice(&(value as u32).to_ne_bytes());
219 }
220 }
221 (6, FIELD_CHECKED) => {
222 if data_len >= 1 {
223 arena.type_data[data_offset] = value as u8;
224 }
225 }
226 _ => return Err(CommandError::UnknownField(field_id)),
227 }
228 Ok(())
229}
230
231fn apply_mdast_bool(
232 arena: &mut Arena,
233 node_id: u32,
234 node_type: u8,
235 field_id: u16,
236 value: bool,
237) -> Result<(), CommandError> {
238 let data_offset = arena.get_node(node_id).data_offset as usize;
239 let data_len = arena.get_node(node_id).data_len as usize;
240 match (node_type, field_id) {
241 (5, FIELD_ORDERED) => {
242 if data_len >= 5 {
243 arena.type_data[data_offset + 4] = value as u8;
244 }
245 }
246 (5, FIELD_SPREAD) => {
247 if data_len >= 6 {
248 arena.type_data[data_offset + 5] = value as u8;
249 }
250 }
251 (6, FIELD_SPREAD) => {
252 if data_len >= 2 {
253 arena.type_data[data_offset + 1] = value as u8;
254 }
255 }
256 _ => return Err(CommandError::UnknownField(field_id)),
257 }
258 Ok(())
259}
260
261fn apply_mdast_null(
262 arena: &mut Arena,
263 node_id: u32,
264 node_type: u8,
265 field_id: u16,
266) -> Result<(), CommandError> {
267 match (node_type, field_id) {
268 (6, FIELD_CHECKED) => {
269 let data_offset = arena.get_node(node_id).data_offset as usize;
270 let data_len = arena.get_node(node_id).data_len as usize;
271 if data_len >= 1 {
272 arena.type_data[data_offset] = 2;
273 }
274 Ok(())
275 }
276 _ => set_string_ref(arena, node_id, field_id, StringRef::empty()),
277 }
278}
279
280fn set_string_ref(
281 arena: &mut Arena,
282 node_id: u32,
283 field_id: u16,
284 sref: StringRef,
285) -> Result<(), CommandError> {
286 let node = arena.get_node(node_id);
287 let node_type = node.node_type;
288 let data_offset = node.data_offset as usize;
289
290 let ref_offset = match (node_type, field_id) {
291 (10 | 13 | 7 | 25 | 26 | 28, FIELD_VALUE) => 0,
293 (15, FIELD_URL) => 0,
295 (15, FIELD_TITLE) => 8,
296 (16, FIELD_URL) => 0,
298 (16, FIELD_ALT) => 8,
299 (16, FIELD_TITLE) => 16,
300 (8, FIELD_LANG) => 0,
302 (8, FIELD_META) => 8,
303 (8, FIELD_VALUE) => 16,
304 (27, FIELD_META) => 0,
306 (27, FIELD_VALUE) => 8,
307 (9, FIELD_URL) => 0,
309 (9, FIELD_TITLE) => 8,
310 (9, FIELD_IDENTIFIER) => 16,
311 (9, FIELD_LABEL) => 24,
312 (17 | 18 | 20, FIELD_IDENTIFIER) => 0,
314 (17 | 18 | 20, FIELD_LABEL) => 8,
315 (19, FIELD_IDENTIFIER) => 0,
317 (19, FIELD_LABEL) => 8,
318 (100 | 101, FIELD_NAME) => 0,
320 (102..=104, FIELD_VALUE) => 0,
322 _ => return Err(CommandError::UnknownField(field_id)),
323 };
324
325 let abs_offset = data_offset + ref_offset;
326 let bytes_offset = sref.offset.to_ne_bytes();
327 let bytes_len = sref.len.to_ne_bytes();
328 arena.type_data[abs_offset..abs_offset + 4].copy_from_slice(&bytes_offset);
329 arena.type_data[abs_offset + 4..abs_offset + 8].copy_from_slice(&bytes_len);
330
331 Ok(())
332}
333
334fn parse_raw_markdown(markdown: &str, parse_markdown: &dyn Fn(&str) -> Arena) -> Arena {
335 parse_markdown(markdown)
336}
337
338fn escape_braces_in_html_text(html: &str) -> String {
346 let mut result = String::with_capacity(html.len());
347 let mut in_tag = false;
348 let mut in_quote: Option<char> = None;
349
350 for ch in html.chars() {
351 if in_tag {
352 match ch {
353 '"' | '\'' if in_quote == Some(ch) => {
354 in_quote = None;
355 result.push(ch);
356 }
357 '"' | '\'' if in_quote.is_none() => {
358 in_quote = Some(ch);
359 result.push(ch);
360 }
361 '>' if in_quote.is_none() => {
362 in_tag = false;
363 result.push(ch);
364 }
365 _ => result.push(ch),
366 }
367 } else {
368 match ch {
369 '<' => {
370 in_tag = true;
371 result.push(ch);
372 }
373 '{' => result.push_str("{'{'}"),
374 '}' => result.push_str("{'}'}"),
375 _ => result.push(ch),
376 }
377 }
378 }
379 result
380}
381
382fn js_node_to_arena(js_node: &JsNode) -> Result<(Arena, bool), CommandError> {
383 let mut builder = ArenaBuilder::new(String::new());
384 emit_js_node(js_node, &mut builder)?;
385 Ok((builder.finish(), js_node.keep_children))
386}
387
388fn emit_js_node(js_node: &JsNode, builder: &mut ArenaBuilder) -> Result<(), CommandError> {
389 if js_node.is_hast {
390 return emit_hast_js_node(js_node, builder);
391 }
392
393 let node_type = name_to_node_type(&js_node.node_type)?;
394 builder.open_node(node_type as u8);
395
396 let type_data = encode_js_node_data(js_node, node_type, builder);
397 if !type_data.is_empty() {
398 builder.set_data_current(&type_data);
399 }
400
401 if let Some(children) = &js_node.children {
402 for child in children {
403 emit_js_node(child, builder)?;
404 }
405 }
406
407 builder.close_node();
408 Ok(())
409}
410
411fn encode_js_node_data(
412 js_node: &JsNode,
413 node_type: MdastNodeType,
414 builder: &mut ArenaBuilder,
415) -> Vec<u8> {
416 match node_type {
417 MdastNodeType::Heading => {
418 let depth = js_node.depth.unwrap_or(1);
419 encode_heading_data(depth)
420 }
421 MdastNodeType::Text
422 | MdastNodeType::InlineCode
423 | MdastNodeType::Html
424 | MdastNodeType::Yaml
425 | MdastNodeType::Toml
426 | MdastNodeType::InlineMath => {
427 let value = js_node.value.as_deref().unwrap_or("");
428 let sref = builder.alloc_string(value);
429 encode_string_ref_data(sref)
430 }
431 MdastNodeType::Code => {
432 let lang_ref = alloc_opt_str(builder, js_node.lang.as_deref());
433 let meta_ref = alloc_opt_str(builder, js_node.meta.as_deref());
434 let value_ref = alloc_opt_str(builder, js_node.value.as_deref());
435 encode_code_data(lang_ref, meta_ref, value_ref, b'`')
436 }
437 MdastNodeType::Math => {
438 let meta_ref = alloc_opt_str(builder, js_node.meta.as_deref());
439 let value_ref = alloc_opt_str(builder, js_node.value.as_deref());
440 encode_math_data(meta_ref, value_ref)
441 }
442 MdastNodeType::Link => {
443 let url_ref = alloc_opt_str(builder, js_node.url.as_deref());
444 let title_ref = alloc_opt_str(builder, js_node.title.as_deref());
445 encode_link_data(url_ref, title_ref)
446 }
447 MdastNodeType::Image => {
448 let url_ref = alloc_opt_str(builder, js_node.url.as_deref());
449 let alt_ref = alloc_opt_str(builder, js_node.alt.as_deref());
450 let title_ref = alloc_opt_str(builder, js_node.title.as_deref());
451 encode_image_data(url_ref, alt_ref, title_ref)
452 }
453 MdastNodeType::Definition => {
454 let url_ref = alloc_opt_str(builder, js_node.url.as_deref());
455 let title_ref = alloc_opt_str(builder, js_node.title.as_deref());
456 let id_ref = alloc_opt_str(builder, js_node.identifier.as_deref());
457 let label_ref = alloc_opt_str(builder, js_node.label.as_deref());
458 encode_definition_data(url_ref, title_ref, id_ref, label_ref)
459 }
460 MdastNodeType::List => {
461 let ordered = js_node.ordered.unwrap_or(false);
462 let start = js_node.start.unwrap_or(1);
463 let spread = js_node.spread.unwrap_or(false);
464 encode_list_data(ordered, start, spread)
465 }
466 MdastNodeType::ListItem => {
467 let checked = match js_node.checked {
468 Some(true) => 1u8,
469 Some(false) => 0u8,
470 None => 2u8, };
472 let spread = js_node.spread.unwrap_or(false);
473 encode_list_item_data(checked, spread)
474 }
475 MdastNodeType::LinkReference
476 | MdastNodeType::ImageReference
477 | MdastNodeType::FootnoteReference => {
478 let id_ref = alloc_opt_str(builder, js_node.identifier.as_deref());
479 let label_ref = alloc_opt_str(builder, js_node.label.as_deref());
480 let kind = match js_node.reference_type.as_deref() {
481 Some("collapsed") => 1u8,
482 Some("full") => 2u8,
483 _ => 0u8, };
485 encode_reference_data(id_ref, label_ref, kind)
486 }
487 MdastNodeType::FootnoteDefinition => {
488 let id_ref = alloc_opt_str(builder, js_node.identifier.as_deref());
489 let label_ref = alloc_opt_str(builder, js_node.label.as_deref());
490 encode_footnote_definition_data(id_ref, label_ref)
491 }
492 MdastNodeType::MdxJsxFlowElement | MdastNodeType::MdxJsxTextElement => {
493 let name_ref = alloc_opt_str(builder, js_node.name.as_deref());
494 let attr_tuples = encode_js_jsx_attrs(builder, js_node.attributes.as_deref());
495 encode_mdx_jsx_element_data(name_ref, &attr_tuples)
496 }
497 MdastNodeType::MdxFlowExpression
498 | MdastNodeType::MdxTextExpression
499 | MdastNodeType::MdxjsEsm => {
500 let value_ref = alloc_opt_str(builder, js_node.value.as_deref());
501 encode_expression_data(value_ref)
502 }
503 _ => Vec::new(),
505 }
506}
507
508fn alloc_opt_str(builder: &mut ArenaBuilder, s: Option<&str>) -> StringRef {
509 match s {
510 Some(v) if !v.is_empty() => builder.alloc_string(v),
511 _ => StringRef::empty(),
512 }
513}
514
515fn name_to_node_type(name: &str) -> Result<MdastNodeType, CommandError> {
516 match name {
517 "root" => Ok(MdastNodeType::Root),
518 "paragraph" => Ok(MdastNodeType::Paragraph),
519 "heading" => Ok(MdastNodeType::Heading),
520 "thematicBreak" => Ok(MdastNodeType::ThematicBreak),
521 "blockquote" => Ok(MdastNodeType::Blockquote),
522 "list" => Ok(MdastNodeType::List),
523 "listItem" => Ok(MdastNodeType::ListItem),
524 "html" => Ok(MdastNodeType::Html),
525 "code" => Ok(MdastNodeType::Code),
526 "definition" => Ok(MdastNodeType::Definition),
527 "text" => Ok(MdastNodeType::Text),
528 "emphasis" => Ok(MdastNodeType::Emphasis),
529 "strong" => Ok(MdastNodeType::Strong),
530 "inlineCode" => Ok(MdastNodeType::InlineCode),
531 "break" => Ok(MdastNodeType::Break),
532 "link" => Ok(MdastNodeType::Link),
533 "image" => Ok(MdastNodeType::Image),
534 "linkReference" => Ok(MdastNodeType::LinkReference),
535 "imageReference" => Ok(MdastNodeType::ImageReference),
536 "footnoteDefinition" => Ok(MdastNodeType::FootnoteDefinition),
537 "footnoteReference" => Ok(MdastNodeType::FootnoteReference),
538 "table" => Ok(MdastNodeType::Table),
539 "tableRow" => Ok(MdastNodeType::TableRow),
540 "tableCell" => Ok(MdastNodeType::TableCell),
541 "delete" => Ok(MdastNodeType::Delete),
542 "yaml" => Ok(MdastNodeType::Yaml),
543 "toml" => Ok(MdastNodeType::Toml),
544 "math" => Ok(MdastNodeType::Math),
545 "inlineMath" => Ok(MdastNodeType::InlineMath),
546 "mdxJsxFlowElement" => Ok(MdastNodeType::MdxJsxFlowElement),
547 "mdxJsxTextElement" => Ok(MdastNodeType::MdxJsxTextElement),
548 "mdxFlowExpression" => Ok(MdastNodeType::MdxFlowExpression),
549 "mdxTextExpression" => Ok(MdastNodeType::MdxTextExpression),
550 "mdxjsEsm" => Ok(MdastNodeType::MdxjsEsm),
551 other => Err(CommandError::UnknownNodeType(other.to_string())),
552 }
553}
554
555fn apply_hast_set_property(
563 arena: &mut Arena,
564 node_id: u32,
565 prop_name: &str,
566 value_type: u8,
567 value_str: &str,
568) -> Option<Result<(), CommandError>> {
569 let node_type = HastNodeType::from_u8(arena.get_node(node_id).node_type)?;
570
571 match node_type {
572 HastNodeType::Element => Some(apply_hast_element_property(
573 arena, node_id, prop_name, value_type, value_str,
574 )),
575
576 HastNodeType::Text
577 | HastNodeType::Comment
578 | HastNodeType::Raw
579 | HastNodeType::MdxFlowExpression
580 | HastNodeType::MdxTextExpression
581 | HastNodeType::MdxEsm
582 if prop_name == "value" =>
583 {
584 let sref = arena.alloc_string(value_str);
585 let data = arena.get_type_data(node_id);
586 if data.len() >= 8 {
587 let data_offset = arena.get_node(node_id).data_offset as usize;
588 arena.type_data[data_offset..data_offset + 4]
589 .copy_from_slice(&sref.offset.to_le_bytes());
590 arena.type_data[data_offset + 4..data_offset + 8]
591 .copy_from_slice(&sref.len.to_le_bytes());
592 Some(Ok(()))
593 } else {
594 Some(Err(CommandError::UnknownField(0)))
595 }
596 }
597
598 _ => None,
599 }
600}
601
602fn apply_hast_element_property(
604 arena: &mut Arena,
605 node_id: u32,
606 prop_name: &str,
607 value_type: u8,
608 value_str: &str,
609) -> Result<(), CommandError> {
610 let old_data = arena.get_type_data(node_id).to_vec();
611 if old_data.len() < 16 {
612 return Err(CommandError::UnexpectedEof);
613 }
614
615 let old_prop_count = u32::from_le_bytes(old_data[8..12].try_into().unwrap()) as usize;
616
617 let mut found_index: Option<usize> = None;
618 for i in 0..old_prop_count {
619 let base = 16 + i * 20;
620 let name_off = u32::from_le_bytes(old_data[base..base + 4].try_into().unwrap());
621 let name_len = u32::from_le_bytes(old_data[base + 4..base + 8].try_into().unwrap());
622 let existing_name = arena.get_str(StringRef::new(name_off, name_len));
623 if existing_name == prop_name {
624 found_index = Some(i);
625 break;
626 }
627 }
628
629 let name_ref = arena.alloc_string(prop_name);
630 let val_ref = if value_str.is_empty() {
631 StringRef::empty()
632 } else {
633 arena.alloc_string(value_str)
634 };
635
636 if let Some(idx) = found_index {
637 let mut new_data = old_data;
638 let base = 16 + idx * 20;
639 new_data[base..base + 4].copy_from_slice(&name_ref.offset.to_le_bytes());
640 new_data[base + 4..base + 8].copy_from_slice(&name_ref.len.to_le_bytes());
641 new_data[base + 8] = value_type;
642 new_data[base + 9..base + 12].copy_from_slice(&[0u8; 3]);
643 new_data[base + 12..base + 16].copy_from_slice(&val_ref.offset.to_le_bytes());
644 new_data[base + 16..base + 20].copy_from_slice(&val_ref.len.to_le_bytes());
645 arena.set_type_data(node_id, &new_data);
646 } else {
647 let new_prop_count = (old_prop_count + 1) as u32;
648 let mut new_data = Vec::with_capacity(16 + new_prop_count as usize * 20);
649 new_data.extend_from_slice(&old_data[0..8]);
650 new_data.extend_from_slice(&new_prop_count.to_le_bytes());
651 new_data.extend_from_slice(&0u32.to_le_bytes());
652 if old_prop_count > 0 {
653 new_data.extend_from_slice(&old_data[16..16 + old_prop_count * 20]);
654 }
655 new_data.extend_from_slice(&name_ref.offset.to_le_bytes());
656 new_data.extend_from_slice(&name_ref.len.to_le_bytes());
657 new_data.push(value_type);
658 new_data.extend_from_slice(&[0u8; 3]);
659 new_data.extend_from_slice(&val_ref.offset.to_le_bytes());
660 new_data.extend_from_slice(&val_ref.len.to_le_bytes());
661 arena.set_type_data(node_id, &new_data);
662 }
663
664 Ok(())
665}
666
667fn emit_hast_js_node(js_node: &JsNode, builder: &mut ArenaBuilder) -> Result<(), CommandError> {
669 let raw_type = name_to_hast_type(&js_node.node_type)
670 .ok_or_else(|| CommandError::UnknownNodeType(js_node.node_type.clone()))?;
671 builder.open_node_raw(raw_type as u8);
672
673 let type_data = encode_hast_js_node_data(js_node, raw_type, builder);
674 if !type_data.is_empty() {
675 builder.set_data_current(&type_data);
676 }
677
678 if let Some(children) = &js_node.children {
679 for child in children {
680 emit_hast_js_node(child, builder)?;
681 }
682 }
683
684 builder.close_node();
685 Ok(())
686}
687
688fn name_to_hast_type(name: &str) -> Option<HastNodeType> {
689 match name {
690 "root" => Some(HastNodeType::Root),
691 "element" => Some(HastNodeType::Element),
692 "text" => Some(HastNodeType::Text),
693 "comment" => Some(HastNodeType::Comment),
694 "doctype" => Some(HastNodeType::Doctype),
695 "raw" => Some(HastNodeType::Raw),
696 "mdxJsxFlowElement" => Some(HastNodeType::MdxJsxElement),
697 "mdxJsxTextElement" => Some(HastNodeType::MdxJsxTextElement),
698 "mdxFlowExpression" => Some(HastNodeType::MdxFlowExpression),
699 "mdxTextExpression" => Some(HastNodeType::MdxTextExpression),
700 "mdxjsEsm" => Some(HastNodeType::MdxEsm),
701 _ => None,
702 }
703}
704
705fn encode_hast_js_node_data(
706 js_node: &JsNode,
707 node_type: HastNodeType,
708 builder: &mut ArenaBuilder,
709) -> Vec<u8> {
710 match node_type {
711 HastNodeType::Element => {
712 let tag = js_node.tag_name.as_deref().unwrap_or("div");
713 let tag_ref = builder.alloc_string(tag);
714
715 let mut props: Vec<(StringRef, u8, StringRef)> = Vec::new();
716 if let Some(properties) = &js_node.properties {
717 for (key, value) in properties {
718 let name_ref = builder.alloc_string(key);
719 match value {
720 serde_json::Value::Bool(true) => {
721 props.push((name_ref, PROP_BOOL_TRUE, StringRef::empty()));
722 }
723 serde_json::Value::Bool(false) => {
724 props.push((name_ref, PROP_BOOL_FALSE, StringRef::empty()));
725 }
726 serde_json::Value::String(s) => {
727 let val_ref = builder.alloc_string(s);
728 props.push((name_ref, PROP_STRING, val_ref));
729 }
730 serde_json::Value::Array(arr) => {
731 let joined: String = arr
732 .iter()
733 .filter_map(|v| v.as_str())
734 .collect::<Vec<_>>()
735 .join(" ");
736 let val_ref = builder.alloc_string(&joined);
737 props.push((name_ref, PROP_SPACE_SEP, val_ref));
738 }
739 _ => {}
740 }
741 }
742 }
743
744 let mut out = Vec::with_capacity(16 + props.len() * 20);
745 out.extend_from_slice(&tag_ref.offset.to_le_bytes());
746 out.extend_from_slice(&tag_ref.len.to_le_bytes());
747 out.extend_from_slice(&(props.len() as u32).to_le_bytes());
748 out.extend_from_slice(&0u32.to_le_bytes());
749 for (name_ref, kind, val_ref) in &props {
750 out.extend_from_slice(&name_ref.offset.to_le_bytes());
751 out.extend_from_slice(&name_ref.len.to_le_bytes());
752 out.push(*kind);
753 out.extend_from_slice(&[0u8; 3]);
754 out.extend_from_slice(&val_ref.offset.to_le_bytes());
755 out.extend_from_slice(&val_ref.len.to_le_bytes());
756 }
757 out
758 }
759
760 HastNodeType::Text | HastNodeType::Comment | HastNodeType::Raw => {
761 let value = js_node.value.as_deref().unwrap_or("");
762 let sref = builder.alloc_string(value);
763 let mut out = [0u8; 8];
764 out[0..4].copy_from_slice(&sref.offset.to_le_bytes());
765 out[4..8].copy_from_slice(&sref.len.to_le_bytes());
766 out.to_vec()
767 }
768
769 HastNodeType::MdxJsxElement | HastNodeType::MdxJsxTextElement => {
770 let name = js_node
771 .name
772 .as_deref()
773 .or(js_node.tag_name.as_deref())
774 .unwrap_or("");
775 let name_ref = builder.alloc_string(name);
776 let attr_tuples = encode_js_jsx_attrs(builder, js_node.attributes.as_deref());
777 encode_mdx_jsx_element_data(name_ref, &attr_tuples)
778 }
779
780 HastNodeType::MdxFlowExpression
781 | HastNodeType::MdxTextExpression
782 | HastNodeType::MdxEsm => {
783 let value = js_node.value.as_deref().unwrap_or("");
784 let sref = builder.alloc_string(value);
785 let mut out = [0u8; 8];
786 out[0..4].copy_from_slice(&sref.offset.to_le_bytes());
787 out[4..8].copy_from_slice(&sref.len.to_le_bytes());
788 out.to_vec()
789 }
790
791 _ => Vec::new(),
792 }
793}
794
795fn read_payload(
797 reader: &mut BufReader<'_>,
798 parse_markdown: &dyn Fn(&str) -> Arena,
799) -> Result<(Arena, bool), CommandError> {
800 let payload_type = reader.read_u8()?;
801 let len = reader.read_u32()? as usize;
802
803 match payload_type {
804 PAYLOAD_RAW_MARKDOWN => {
805 let md = reader.read_str(len)?;
806 Ok((parse_raw_markdown(md, parse_markdown), false))
807 }
808 PAYLOAD_RAW_HTML => {
809 let html = reader.read_str(len)?;
810 let escaped = escape_braces_in_html_text(html);
811 Ok((parse_raw_markdown(&escaped, parse_markdown), false))
812 }
813 PAYLOAD_SERDE_JSON => {
814 let json_str = reader.read_str(len)?;
815 let js_node: JsNode = serde_json::from_str(json_str)
816 .map_err(|e| CommandError::InvalidJson(e.to_string()))?;
817 js_node_to_arena(&js_node)
818 }
819 other => Err(CommandError::UnknownPayloadType(other)),
820 }
821}
822
823pub fn apply_commands(
829 mut arena: Arena,
830 command_buf: &[u8],
831 parse_markdown: &dyn Fn(&str) -> Arena,
832) -> Result<Arena, CommandError> {
833 if command_buf.is_empty() {
834 return Ok(arena);
835 }
836
837 let mut patches: Vec<Patch> = Vec::new();
838 let mut reader = BufReader::new(command_buf);
839
840 while reader.remaining() > 0 {
841 let cmd = reader.read_u8()?;
842
843 match cmd {
844 CMD_REMOVE => {
845 let node_id = reader.read_u32()?;
846 patches.push(Patch::Remove { node_id });
847 }
848
849 CMD_SET_PROPERTY => {
850 let node_id = reader.read_u32()?;
851 let value_type = reader.read_u8()?;
852 let name_len = reader.read_u32()? as usize;
853 let name = reader.read_str(name_len)?;
854 let value_len = reader.read_u32()? as usize;
855 let value = reader.read_str(value_len)?;
856 apply_set_property(&mut arena, node_id, name, value_type, value)?;
857 }
858
859 CMD_INSERT_BEFORE => {
860 let node_id = reader.read_u32()?;
861 let (new_tree, _) = read_payload(&mut reader, parse_markdown)?;
862 patches.push(Patch::InsertBefore { node_id, new_tree });
863 }
864
865 CMD_INSERT_AFTER => {
866 let node_id = reader.read_u32()?;
867 let (new_tree, _) = read_payload(&mut reader, parse_markdown)?;
868 patches.push(Patch::InsertAfter { node_id, new_tree });
869 }
870
871 CMD_PREPEND_CHILD => {
872 let node_id = reader.read_u32()?;
873 let (child_tree, _) = read_payload(&mut reader, parse_markdown)?;
874 patches.push(Patch::PrependChild {
875 node_id,
876 child_tree,
877 });
878 }
879
880 CMD_APPEND_CHILD => {
881 let node_id = reader.read_u32()?;
882 let (child_tree, _) = read_payload(&mut reader, parse_markdown)?;
883 patches.push(Patch::AppendChild {
884 node_id,
885 child_tree,
886 });
887 }
888
889 CMD_WRAP => {
890 let node_id = reader.read_u32()?;
891 let (parent_tree, _) = read_payload(&mut reader, parse_markdown)?;
892 patches.push(Patch::Wrap {
893 node_id,
894 parent_tree,
895 });
896 }
897
898 CMD_REPLACE => {
899 let node_id = reader.read_u32()?;
900 let (new_tree, keep_children) = read_payload(&mut reader, parse_markdown)?;
901 patches.push(Patch::Replace {
902 node_id,
903 new_tree,
904 keep_children,
905 });
906 }
907
908 other => return Err(CommandError::UnknownCommand(other)),
909 }
910 }
911
912 if patches.is_empty() {
913 Ok(arena)
914 } else {
915 Ok(satteri_ast::rebuild::rebuild(&arena, &patches))
916 }
917}
918
919#[cfg(test)]
920mod tests {
921 use super::*;
922 use satteri_ast::shared::PROP_INT;
923
924 fn test_parse_markdown(source: &str) -> Arena {
925 let mut b = ArenaBuilder::new(String::new());
926 b.open_node(MdastNodeType::Root as u8);
927 b.open_node(MdastNodeType::Paragraph as u8);
928 b.open_node(MdastNodeType::Text as u8);
929 let sref = b.alloc_string(source);
930 b.set_data_current(&satteri_arena::encode_string_ref_data(sref));
931 b.close_node();
932 b.close_node();
933 b.close_node();
934 b.finish()
935 }
936
937 fn push_u32(buf: &mut Vec<u8>, v: u32) {
938 buf.extend_from_slice(&v.to_le_bytes());
939 }
940
941 fn push_set_property(buf: &mut Vec<u8>, node_id: u32, value_type: u8, name: &str, value: &str) {
943 buf.push(CMD_SET_PROPERTY);
944 push_u32(buf, node_id);
945 buf.push(value_type);
946 push_u32(buf, name.len() as u32);
947 buf.extend_from_slice(name.as_bytes());
948 push_u32(buf, value.len() as u32);
949 buf.extend_from_slice(value.as_bytes());
950 }
951
952 fn build_hello_world() -> Arena {
953 use satteri_ast::mdast::codec::{encode_heading_data, encode_string_ref_data};
954
955 let source = "# Hello\n\nWorld".to_string();
956 let mut b = ArenaBuilder::new(source);
957
958 b.open_node(MdastNodeType::Root as u8);
959 b.set_position_current(0, 14, 1, 1, 2, 6);
960
961 b.open_node(MdastNodeType::Heading as u8);
962 b.set_position_current(0, 7, 1, 1, 1, 8);
963 b.set_data_current(&encode_heading_data(1));
964
965 b.open_node(MdastNodeType::Text as u8);
966 b.set_position_current(2, 7, 1, 3, 1, 8);
967 b.set_data_current(&encode_string_ref_data(StringRef::new(2, 5)));
968 b.close_node();
969
970 b.close_node();
971
972 b.open_node(MdastNodeType::Paragraph as u8);
973 b.set_position_current(9, 14, 2, 1, 2, 6);
974
975 b.open_node(MdastNodeType::Text as u8);
976 b.set_position_current(9, 14, 2, 1, 2, 6);
977 b.set_data_current(&encode_string_ref_data(StringRef::new(9, 5)));
978 b.close_node();
979
980 b.close_node();
981 b.close_node();
982
983 b.finish()
984 }
985
986 #[test]
987 fn empty_command_buffer() {
988 let arena = build_hello_world();
989 let result = apply_commands(arena.clone(), &[], &test_parse_markdown).unwrap();
990 assert_eq!(result.len(), arena.len());
991 }
992
993 #[test]
994 fn remove_command() {
995 let arena = build_hello_world();
996 let heading_id = arena.get_children(0)[0];
997 let mut buf = Vec::new();
998 buf.push(CMD_REMOVE);
999 push_u32(&mut buf, heading_id);
1000
1001 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1002 assert_eq!(result.get_children(0).len(), 1);
1003 assert_eq!(
1004 result.get_node(result.get_children(0)[0]).node_type,
1005 MdastNodeType::Paragraph as u8
1006 );
1007 }
1008
1009 #[test]
1010 fn set_property_heading_depth() {
1011 let arena = build_hello_world();
1012 let heading_id = arena.get_children(0)[0];
1013
1014 let mut buf = Vec::new();
1015 push_set_property(&mut buf, heading_id, PROP_INT, "depth", "3");
1016
1017 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1018 let heading_data = result.get_type_data(heading_id);
1019 let heading = decode_heading_data(heading_data);
1020 assert_eq!(heading.depth, 3);
1021 }
1022
1023 #[test]
1024 fn set_property_text_value() {
1025 let arena = build_hello_world();
1026 let heading_id = arena.get_children(0)[0];
1027 let text_id = arena.get_children(heading_id)[0];
1028
1029 let mut buf = Vec::new();
1030 push_set_property(&mut buf, text_id, PROP_STRING, "value", "Goodbye");
1031
1032 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1033 let text_data = result.get_type_data(text_id);
1034 let sref = decode_string_ref_data(text_data);
1035 assert_eq!(result.get_str(sref), "Goodbye");
1036 }
1037
1038 #[test]
1039 fn replace_with_raw_markdown() {
1040 let arena = build_hello_world();
1041 let heading_id = arena.get_children(0)[0];
1042
1043 let raw_md = "## New Heading";
1044 let mut buf = Vec::new();
1045 buf.push(CMD_REPLACE);
1046 push_u32(&mut buf, heading_id);
1047 buf.push(PAYLOAD_RAW_MARKDOWN);
1048 push_u32(&mut buf, raw_md.len() as u32);
1049 buf.extend_from_slice(raw_md.as_bytes());
1050
1051 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1052 let root_children = result.get_children(0);
1053 assert!(root_children.len() >= 2);
1054 }
1055
1056 #[test]
1057 fn replace_with_serde_json() {
1058 let arena = build_hello_world();
1059 let heading_id = arena.get_children(0)[0];
1060
1061 let json =
1062 r#"{"type":"heading","depth":2,"children":[{"type":"text","value":"Replaced"}]}"#;
1063 let mut buf = Vec::new();
1064 buf.push(CMD_REPLACE);
1065 push_u32(&mut buf, heading_id);
1066 buf.push(PAYLOAD_SERDE_JSON);
1067 push_u32(&mut buf, json.len() as u32);
1068 buf.extend_from_slice(json.as_bytes());
1069
1070 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1071 let root_children = result.get_children(0);
1072 assert_eq!(root_children.len(), 2);
1073 let new_heading = root_children[0];
1074 assert_eq!(
1075 result.get_node(new_heading).node_type,
1076 MdastNodeType::Heading as u8
1077 );
1078 let heading_data = result.get_type_data(new_heading);
1079 assert_eq!(decode_heading_data(heading_data).depth, 2);
1080 }
1081
1082 #[test]
1083 fn multiple_commands() {
1084 let arena = build_hello_world();
1085 let heading_id = arena.get_children(0)[0];
1086 let text_id = arena.get_children(heading_id)[0];
1087
1088 let mut buf = Vec::new();
1089 push_set_property(&mut buf, heading_id, PROP_INT, "depth", "3");
1090 push_set_property(&mut buf, text_id, PROP_STRING, "value", "Hi");
1091
1092 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1093
1094 let heading_data = result.get_type_data(heading_id);
1095 assert_eq!(decode_heading_data(heading_data).depth, 3);
1096
1097 let text_data = result.get_type_data(text_id);
1098 let sref = decode_string_ref_data(text_data);
1099 assert_eq!(result.get_str(sref), "Hi");
1100 }
1101
1102 #[test]
1103 fn set_property_null() {
1104 let arena = build_hello_world();
1105 let heading_id = arena.get_children(0)[0];
1106 let text_id = arena.get_children(heading_id)[0];
1107
1108 let mut buf = Vec::new();
1109 push_set_property(&mut buf, text_id, PROP_NULL, "value", "");
1110
1111 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1112 let text_data = result.get_type_data(text_id);
1113 let sref = decode_string_ref_data(text_data);
1114 assert_eq!(sref.len, 0);
1115 }
1116
1117 #[test]
1118 fn js_node_to_arena_basic() {
1119 let js = JsNode {
1120 node_type: "heading".to_string(),
1121 children: Some(vec![JsNode {
1122 node_type: "text".to_string(),
1123 children: None,
1124 value: Some("Hello".to_string()),
1125 depth: None,
1126 url: None,
1127 title: None,
1128 alt: None,
1129 lang: None,
1130 meta: None,
1131 ordered: None,
1132 start: None,
1133 spread: None,
1134 checked: None,
1135 identifier: None,
1136 label: None,
1137 reference_type: None,
1138 name: None,
1139 attributes: None,
1140 tag_name: None,
1141 properties: None,
1142 is_hast: false,
1143 keep_children: false,
1144 }]),
1145 depth: Some(2),
1146 value: None,
1147 url: None,
1148 title: None,
1149 alt: None,
1150 lang: None,
1151 meta: None,
1152 ordered: None,
1153 start: None,
1154 spread: None,
1155 checked: None,
1156 identifier: None,
1157 label: None,
1158 reference_type: None,
1159 name: None,
1160 attributes: None,
1161 tag_name: None,
1162 properties: None,
1163 is_hast: false,
1164 keep_children: false,
1165 };
1166
1167 let (arena, _keep) = js_node_to_arena(&js).unwrap();
1168 assert_eq!(arena.len(), 2);
1169 assert_eq!(arena.get_node(0).node_type, MdastNodeType::Heading as u8);
1170 assert_eq!(arena.get_children(0).len(), 1);
1171 let text_id = arena.get_children(0)[0];
1172 assert_eq!(arena.get_node(text_id).node_type, MdastNodeType::Text as u8);
1173 }
1174
1175 #[test]
1176 fn escape_braces_in_html_text_basic() {
1177 assert_eq!(
1178 escape_braces_in_html_text("<span>{foo: 1}</span>"),
1179 "<span>{'{'}foo: 1{'}'}</span>"
1180 );
1181 }
1182
1183 #[test]
1184 fn escape_braces_preserves_attributes() {
1185 let result = escape_braces_in_html_text(r#"<span data-x="{a}">{b}</span>"#);
1186 assert!(
1187 result.contains(r#"data-x="{a}""#),
1188 "attribute braces preserved"
1189 );
1190 assert!(result.contains("{'{'}"), "text braces escaped");
1191 }
1192
1193 #[test]
1194 fn escape_braces_no_braces() {
1195 let html = r#"<pre class="shiki"><code><span style="color:red">hello</span></code></pre>"#;
1196 assert_eq!(escape_braces_in_html_text(html), html);
1197 }
1198
1199 #[test]
1200 fn escape_braces_shiki_output() {
1201 let html = r#"<pre class="shiki"><code><span style="color:#E1E4E8">const x = </span><span style="color:#B392F0">{</span><span style="color:#E1E4E8">foo: 1</span><span style="color:#B392F0">}</span></code></pre>"#;
1202 let escaped = escape_braces_in_html_text(html);
1203 assert!(
1204 !escaped.contains(">{<"),
1205 "bare braces in text should be escaped"
1206 );
1207 assert!(
1208 !escaped.contains(">}<"),
1209 "bare braces in text should be escaped"
1210 );
1211 assert!(escaped.contains(r#"class="shiki""#));
1212 assert!(escaped.contains(r#"style="color:#E1E4E8""#));
1213 }
1214
1215 #[test]
1216 fn hast_set_property_add_new() {
1217 let arena = build_hast_element(&[]);
1218 let element_id = arena.get_children(0)[0];
1219
1220 let mut buf = Vec::new();
1221 push_set_property(&mut buf, element_id, PROP_STRING, "class", "test");
1222
1223 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1224 let data = result.get_type_data(element_id);
1225 let prop_count = u32::from_le_bytes(data[8..12].try_into().unwrap());
1226 assert_eq!(prop_count, 1);
1227 let name_ref = StringRef::new(
1228 u32::from_le_bytes(data[16..20].try_into().unwrap()),
1229 u32::from_le_bytes(data[20..24].try_into().unwrap()),
1230 );
1231 assert_eq!(result.get_str(name_ref), "class");
1232 let val_ref = StringRef::new(
1233 u32::from_le_bytes(data[28..32].try_into().unwrap()),
1234 u32::from_le_bytes(data[32..36].try_into().unwrap()),
1235 );
1236 assert_eq!(result.get_str(val_ref), "test");
1237 assert_eq!(data[24], PROP_STRING);
1238 }
1239
1240 #[test]
1241 fn hast_set_property_overwrite_existing() {
1242 let arena = build_hast_element(&[("class", PROP_STRING, "old")]);
1243 let element_id = arena.get_children(0)[0];
1244
1245 let mut buf = Vec::new();
1246 push_set_property(&mut buf, element_id, PROP_STRING, "class", "new-value");
1247
1248 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1249 let data = result.get_type_data(element_id);
1250 let prop_count = u32::from_le_bytes(data[8..12].try_into().unwrap());
1251 assert_eq!(prop_count, 1);
1252 let val_ref = StringRef::new(
1253 u32::from_le_bytes(data[28..32].try_into().unwrap()),
1254 u32::from_le_bytes(data[32..36].try_into().unwrap()),
1255 );
1256 assert_eq!(result.get_str(val_ref), "new-value");
1257 }
1258
1259 #[test]
1260 fn hast_set_property_bool_true() {
1261 let arena = build_hast_element(&[]);
1262 let element_id = arena.get_children(0)[0];
1263
1264 let mut buf = Vec::new();
1265 push_set_property(&mut buf, element_id, PROP_BOOL_TRUE, "disabled", "");
1266
1267 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1268 let data = result.get_type_data(element_id);
1269 let prop_count = u32::from_le_bytes(data[8..12].try_into().unwrap());
1270 assert_eq!(prop_count, 1);
1271 assert_eq!(data[24], PROP_BOOL_TRUE);
1272 }
1273
1274 #[test]
1275 fn hast_set_property_multiple_on_same_node() {
1276 let arena = build_hast_element(&[]);
1277 let element_id = arena.get_children(0)[0];
1278
1279 let mut buf = Vec::new();
1280 push_set_property(&mut buf, element_id, PROP_STRING, "class", "foo");
1281 push_set_property(&mut buf, element_id, PROP_STRING, "id", "bar");
1282
1283 let result = apply_commands(arena.clone(), &buf, &test_parse_markdown).unwrap();
1284 let data = result.get_type_data(element_id);
1285 let prop_count = u32::from_le_bytes(data[8..12].try_into().unwrap());
1286 assert_eq!(prop_count, 2);
1287 }
1288
1289 fn build_hast_element(props: &[(&str, u8, &str)]) -> Arena {
1291 use satteri_ast::hast::node::HastNodeType;
1292
1293 let mut b = ArenaBuilder::new(String::new());
1294 b.open_node_raw(HastNodeType::Root as u8);
1295 b.open_node_raw(HastNodeType::Element as u8);
1296 let tag_ref = b.alloc_string("div");
1297 let prop_tuples: Vec<(StringRef, u8, StringRef)> = props
1298 .iter()
1299 .map(|(name, kind, value)| {
1300 let n = b.alloc_string(name);
1301 let v = if value.is_empty() {
1302 StringRef::empty()
1303 } else {
1304 b.alloc_string(value)
1305 };
1306 (n, *kind, v)
1307 })
1308 .collect();
1309 let mut type_data = Vec::with_capacity(16 + prop_tuples.len() * 20);
1310 type_data.extend_from_slice(&tag_ref.offset.to_le_bytes());
1311 type_data.extend_from_slice(&tag_ref.len.to_le_bytes());
1312 type_data.extend_from_slice(&(prop_tuples.len() as u32).to_le_bytes());
1313 type_data.extend_from_slice(&0u32.to_le_bytes());
1314 for (n, kind, v) in &prop_tuples {
1315 type_data.extend_from_slice(&n.offset.to_le_bytes());
1316 type_data.extend_from_slice(&n.len.to_le_bytes());
1317 type_data.push(*kind);
1318 type_data.extend_from_slice(&[0u8; 3]);
1319 type_data.extend_from_slice(&v.offset.to_le_bytes());
1320 type_data.extend_from_slice(&v.len.to_le_bytes());
1321 }
1322 b.set_data_current(&type_data);
1323 b.close_node();
1324 b.close_node();
1325 b.finish()
1326 }
1327}