1use std::fmt;
6
7use blitz_dom::node::NodeData;
8use blitz_dom::{BaseDocument, NodeId};
9
10use crate::Result;
11use crate::error::DomError;
12
13pub mod document_position {
15 pub const DISCONNECTED: u16 = 0x01;
17 pub const PRECEDING: u16 = 0x02;
19 pub const FOLLOWING: u16 = 0x04;
21 pub const CONTAINS: u16 = 0x08;
23 pub const CONTAINED_BY: u16 = 0x10;
25 pub const IMPLEMENTATION_SPECIFIC: u16 = 0x20;
27}
28
29pub fn parent_node(doc: &BaseDocument, node: NodeId) -> Result<Option<NodeId>> {
33 Ok(doc.get_node(node).and_then(|node| node.parent))
34}
35
36pub fn child_nodes(doc: &BaseDocument, node: NodeId) -> Result<Vec<NodeId>> {
41 Ok(doc
42 .get_node(node)
43 .map(|node| node.children.to_vec())
44 .unwrap_or_default())
45}
46
47pub fn first_child(doc: &BaseDocument, node: NodeId) -> Result<Option<NodeId>> {
49 Ok(doc
50 .get_node(node)
51 .and_then(|node| node.children.first().copied()))
52}
53
54pub fn previous_sibling(doc: &BaseDocument, node: NodeId) -> Result<Option<NodeId>> {
56 Ok(sibling(doc, node, -1))
57}
58
59pub fn next_sibling(doc: &BaseDocument, node: NodeId) -> Result<Option<NodeId>> {
61 Ok(sibling(doc, node, 1))
62}
63
64fn sibling(doc: &BaseDocument, node_id: NodeId, offset: isize) -> Option<NodeId> {
65 let node = doc.get_node(node_id)?;
66 let parent = doc.get_node(node.parent?)?;
67 let index = parent.index_of_child(node_id)?;
68 let sibling_index = index.checked_add_signed(offset)?;
69 parent.children.get(sibling_index).copied()
70}
71
72pub fn has_child_nodes(doc: &BaseDocument, node: NodeId) -> Result<bool> {
74 Ok(doc
75 .get_node(node)
76 .is_some_and(|node| !node.children.is_empty()))
77}
78
79pub fn contains(doc: &BaseDocument, node: NodeId, other: NodeId) -> Result<bool> {
84 let mut current = other;
85 loop {
86 if current == node {
87 return Ok(true);
88 }
89 match doc.get_node(current).and_then(|node| node.parent) {
90 Some(parent_id) => current = parent_id,
91 None => return Ok(false),
92 }
93 }
94}
95
96pub fn compare_document_position(doc: &BaseDocument, node: NodeId, other: NodeId) -> Result<u16> {
103 use document_position::*;
104
105 if node == other {
106 return Ok(0);
107 }
108
109 let path = |mut id: NodeId| {
110 let mut path = Vec::new();
111 loop {
112 path.push(id);
113 let Some(parent) = doc.get_node(id).and_then(|node| node.parent) else {
114 break;
115 };
116 id = parent;
117 }
118 path.reverse();
119 path
120 };
121 let node_path = path(node);
122 let other_path = path(other);
123 if node_path.first() != other_path.first() {
124 let order = if node < other { FOLLOWING } else { PRECEDING };
125 return Ok(DISCONNECTED | IMPLEMENTATION_SPECIFIC | order);
126 }
127
128 let common = node_path
129 .iter()
130 .zip(&other_path)
131 .take_while(|(left, right)| left == right)
132 .count();
133 if common == node_path.len() {
134 return Ok(FOLLOWING | CONTAINED_BY);
135 }
136 if common == other_path.len() {
137 return Ok(PRECEDING | CONTAINS);
138 }
139 let parent_id = node_path[common - 1];
140 let parent = doc
141 .get_node(parent_id)
142 .ok_or(DomError::TreeInvariant("common ancestor is missing"))?;
143 let node_index = parent
144 .children
145 .iter()
146 .position(|child| *child == node_path[common])
147 .ok_or(DomError::TreeInvariant("node missing from common ancestor"))?;
148 let other_index = parent
149 .children
150 .iter()
151 .position(|child| *child == other_path[common])
152 .ok_or(DomError::TreeInvariant(
153 "other node missing from common ancestor",
154 ))?;
155 Ok(if node_index < other_index {
156 FOLLOWING
157 } else {
158 PRECEDING
159 })
160}
161
162pub fn text_content(doc: &BaseDocument, node: NodeId) -> Result<String> {
169 Ok(doc
170 .get_node(node)
171 .map(|node| node.text_content())
172 .unwrap_or_default())
173}
174
175struct Counting(usize);
177
178impl fmt::Write for Counting {
179 fn write_str(&mut self, s: &str) -> fmt::Result {
180 self.0 += s.len();
181 Ok(())
182 }
183}
184
185struct Filling<'out> {
190 out: &'out mut [u8],
191 at: usize,
192}
193
194impl fmt::Write for Filling<'_> {
195 fn write_str(&mut self, s: &str) -> fmt::Result {
196 let end = self.at + s.len();
197 debug_assert!(end <= self.out.len(), "measured length was short");
198 self.out[self.at..end].copy_from_slice(s.as_bytes());
199 self.at = end;
200 Ok(())
201 }
202}
203
204pub fn text_content_len(doc: &BaseDocument, node: NodeId) -> Result<usize> {
209 let mut counting = Counting(0);
210 if let Some(node) = doc.get_node(node) {
211 node.write_text_content(&mut counting);
212 }
213 Ok(counting.0)
214}
215
216pub fn text_content_into(doc: &BaseDocument, node: NodeId, out: &mut [u8]) -> Result<usize> {
243 let len = text_content_len(doc, node)?;
244 if len > out.len() {
245 return Ok(len);
246 }
247 if let Some(node) = doc.get_node(node) {
248 let mut filling = Filling { out, at: 0 };
249 node.write_text_content(&mut filling);
250 }
251 Ok(len)
252}
253
254pub fn set_text_content(doc: &mut BaseDocument, node: NodeId, text: &str) -> Result<()> {
263 let is_text_like = matches!(
264 doc.get_node(node).map(|node| &node.data),
265 Some(NodeData::Text(_)) | Some(NodeData::Comment { .. })
266 );
267
268 let mut mutr = doc.mutate();
269 if is_text_like {
270 mutr.set_node_text(node, text);
271 } else {
272 for child_id in mutr.child_ids(node) {
273 mutr.remove_node(child_id);
274 }
275 if !text.is_empty() {
276 let text_id = mutr.create_text_node(text);
277 mutr.append_children(node, &[text_id]);
278 }
279 }
280 Ok(())
281}
282
283pub fn append_child(doc: &mut BaseDocument, parent: NodeId, child: NodeId) -> Result<NodeId> {
294 let mut mutr = doc.mutate();
295 if mutr.node_has_parent(child) {
296 mutr.remove_node(child);
297 }
298 mutr.append_children(parent, &[child]);
299 Ok(child)
300}
301
302pub fn insert_before(
307 doc: &mut BaseDocument,
308 parent: NodeId,
309 new_node: NodeId,
310 reference: Option<NodeId>,
311) -> Result<NodeId> {
312 if reference == Some(new_node) {
313 return Ok(new_node);
314 }
315
316 let mut mutr = doc.mutate();
317 if mutr.node_has_parent(new_node) {
318 mutr.remove_node(new_node);
319 }
320 match reference {
321 Some(reference) if mutr.node_has_parent(reference) => {
322 mutr.insert_nodes_before(reference, &[new_node]);
323 }
324 _ => mutr.append_children(parent, &[new_node]),
325 }
326 Ok(new_node)
327}
328
329pub fn remove_child(doc: &mut BaseDocument, parent: NodeId, child: NodeId) -> Result<NodeId> {
336 let _ = parent;
337 doc.mutate().remove_node(child);
338 Ok(child)
339}
340
341pub fn replace_child(
346 doc: &mut BaseDocument,
347 parent: NodeId,
348 new_node: NodeId,
349 old_node: NodeId,
350) -> Result<NodeId> {
351 let _ = parent;
352 if new_node != old_node {
353 let mut mutr = doc.mutate();
354 if mutr.node_has_parent(new_node) {
355 mutr.remove_node(new_node);
356 }
357 mutr.insert_nodes_before(old_node, &[new_node]);
358 mutr.remove_node(old_node);
359 }
360 Ok(old_node)
361}
362
363pub fn clone_node(doc: &mut BaseDocument, node: NodeId, deep: bool) -> Result<NodeId> {
369 enum CloneSrc {
370 Element(blitz_dom::QualName, Vec<blitz_dom::Attribute>),
371 Text(String),
372 Comment(String),
373 Other,
374 }
375
376 if deep {
377 return Ok(doc.mutate().deep_clone_node(node));
378 }
379
380 let src = match doc.get_node(node).map(|node| &node.data) {
381 Some(NodeData::Element(data)) => {
382 CloneSrc::Element(data.name.clone(), data.attrs().to_vec())
383 }
384 Some(NodeData::Text(data)) => CloneSrc::Text(data.content.clone()),
385 Some(NodeData::Comment { contents }) => CloneSrc::Comment(contents.clone()),
386 _ => CloneSrc::Other,
387 };
388 let mut mutr = doc.mutate();
389 Ok(match src {
390 CloneSrc::Element(name, attrs) => mutr.create_element(name, attrs),
391 CloneSrc::Text(content) => mutr.create_text_node(&content),
392 CloneSrc::Comment(contents) => mutr.create_comment_node(&contents),
393 CloneSrc::Other => mutr.create_comment_node(""),
394 })
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400 use crate::document;
401 use crate::element;
402 use crate::test_support::skeleton;
403
404 fn div(doc: &mut BaseDocument) -> NodeId {
405 document::create_element(doc, "div").unwrap()
406 }
407
408 #[test]
409 fn append_child_attaches_and_returns_the_child() {
410 let (mut doc, _html, _head, body) = skeleton();
411 let child = div(&mut doc);
412 assert_eq!(append_child(&mut doc, body, child).unwrap(), child);
413 assert_eq!(parent_node(&doc, child).unwrap(), Some(body));
414 }
415
416 #[test]
419 fn append_child_moves_an_already_attached_node() {
420 let (mut doc, _html, _head, body) = skeleton();
421 let first = div(&mut doc);
422 let second = div(&mut doc);
423 let child = div(&mut doc);
424 append_child(&mut doc, body, first).unwrap();
425 append_child(&mut doc, body, second).unwrap();
426 append_child(&mut doc, first, child).unwrap();
427 append_child(&mut doc, second, child).unwrap();
428
429 assert_eq!(parent_node(&doc, child).unwrap(), Some(second));
430 assert!(child_nodes(&doc, first).unwrap().is_empty());
431 }
432
433 #[test]
434 fn insert_before_places_the_node_ahead_of_the_reference() {
435 let (mut doc, _html, _head, body) = skeleton();
436 let first = div(&mut doc);
437 let second = div(&mut doc);
438 append_child(&mut doc, body, first).unwrap();
439 insert_before(&mut doc, body, second, Some(first)).unwrap();
440 assert_eq!(child_nodes(&doc, body).unwrap(), vec![second, first]);
441 }
442
443 #[test]
444 fn insert_before_with_no_reference_appends() {
445 let (mut doc, _html, _head, body) = skeleton();
446 let first = div(&mut doc);
447 let second = div(&mut doc);
448 append_child(&mut doc, body, first).unwrap();
449 insert_before(&mut doc, body, second, None).unwrap();
450 assert_eq!(child_nodes(&doc, body).unwrap(), vec![first, second]);
451 }
452
453 #[test]
454 fn insert_before_itself_is_a_no_op() {
455 let (mut doc, _html, _head, body) = skeleton();
456 let only = div(&mut doc);
457 append_child(&mut doc, body, only).unwrap();
458 insert_before(&mut doc, body, only, Some(only)).unwrap();
459 assert_eq!(child_nodes(&doc, body).unwrap(), vec![only]);
460 }
461
462 #[test]
463 fn remove_child_detaches_and_returns_the_child() {
464 let (mut doc, _html, _head, body) = skeleton();
465 let child = div(&mut doc);
466 append_child(&mut doc, body, child).unwrap();
467 assert_eq!(remove_child(&mut doc, body, child).unwrap(), child);
468 assert_eq!(parent_node(&doc, child).unwrap(), None);
469 assert!(doc.get_node(child).is_some());
471 }
472
473 #[test]
474 fn replace_child_swaps_in_place_and_returns_the_old_node() {
475 let (mut doc, _html, _head, body) = skeleton();
476 let before = div(&mut doc);
477 let old = div(&mut doc);
478 let after = div(&mut doc);
479 let new = div(&mut doc);
480 for id in [before, old, after] {
481 append_child(&mut doc, body, id).unwrap();
482 }
483 assert_eq!(replace_child(&mut doc, body, new, old).unwrap(), old);
484 assert_eq!(child_nodes(&doc, body).unwrap(), vec![before, new, after]);
485 assert_eq!(parent_node(&doc, old).unwrap(), None);
486 }
487
488 #[test]
489 fn first_child_and_siblings_walk_the_child_list() {
490 let (mut doc, _html, _head, body) = skeleton();
491 let a = div(&mut doc);
492 let b = div(&mut doc);
493 let c = div(&mut doc);
494 for id in [a, b, c] {
495 append_child(&mut doc, body, id).unwrap();
496 }
497 assert_eq!(first_child(&doc, body).unwrap(), Some(a));
498 assert_eq!(next_sibling(&doc, a).unwrap(), Some(b));
499 assert_eq!(next_sibling(&doc, c).unwrap(), None);
500 assert_eq!(previous_sibling(&doc, b).unwrap(), Some(a));
501 assert_eq!(previous_sibling(&doc, a).unwrap(), None);
502 }
503
504 #[test]
505 fn parent_node_is_none_for_a_detached_node() {
506 let (mut doc, _html, _head, _body) = skeleton();
507 let orphan = div(&mut doc);
508 assert_eq!(parent_node(&doc, orphan).unwrap(), None);
509 }
510
511 #[test]
512 fn child_nodes_lists_every_child_including_text() {
513 let (mut doc, _html, _head, body) = skeleton();
514 let element = div(&mut doc);
515 let text = document::create_text_node(&mut doc, "x").unwrap();
516 append_child(&mut doc, body, element).unwrap();
517 append_child(&mut doc, body, text).unwrap();
518 assert_eq!(child_nodes(&doc, body).unwrap(), vec![element, text]);
519 }
520
521 #[test]
522 fn has_child_nodes_tracks_the_child_list() {
523 let (mut doc, _html, _head, body) = skeleton();
524 assert!(!has_child_nodes(&doc, body).unwrap());
525 let child = div(&mut doc);
526 append_child(&mut doc, body, child).unwrap();
527 assert!(has_child_nodes(&doc, body).unwrap());
528 }
529
530 #[test]
531 fn contains_is_inclusive_and_transitive() {
532 let (mut doc, _html, _head, body) = skeleton();
533 let outer = div(&mut doc);
534 let inner = div(&mut doc);
535 let detached = div(&mut doc);
536 append_child(&mut doc, body, outer).unwrap();
537 append_child(&mut doc, outer, inner).unwrap();
538
539 assert!(contains(&doc, outer, outer).unwrap());
540 assert!(contains(&doc, outer, inner).unwrap());
541 assert!(contains(&doc, body, inner).unwrap());
542 assert!(!contains(&doc, inner, outer).unwrap());
543 assert!(!contains(&doc, outer, detached).unwrap());
544 }
545
546 #[test]
547 fn clone_node_copies_attributes_and_honours_deep() {
548 let (mut doc, _html, _head, body) = skeleton();
549 let outer = div(&mut doc);
550 let inner = div(&mut doc);
551 element::set_attribute(&mut doc, outer, "class", "panel").unwrap();
552 append_child(&mut doc, outer, inner).unwrap();
553 append_child(&mut doc, body, outer).unwrap();
554
555 let shallow = clone_node(&mut doc, outer, false).unwrap();
556 assert_eq!(
557 element::get_attribute(&doc, shallow, "class").unwrap(),
558 Some("panel".to_string())
559 );
560 assert!(child_nodes(&doc, shallow).unwrap().is_empty());
561
562 let deep = clone_node(&mut doc, outer, true).unwrap();
563 assert_eq!(child_nodes(&doc, deep).unwrap().len(), 1);
564 }
565
566 #[test]
567 fn clone_node_copies_text_and_comment_contents() {
568 let (mut doc, _html, _head, _body) = skeleton();
569 let text = document::create_text_node(&mut doc, "hello").unwrap();
570 let comment = document::create_comment(&mut doc, "note").unwrap();
571 let text_copy = clone_node(&mut doc, text, false).unwrap();
572 let comment_copy = clone_node(&mut doc, comment, false).unwrap();
573 assert_eq!(text_content(&doc, text_copy).unwrap(), "hello");
574 assert!(matches!(
575 doc.get_node(comment_copy).map(|n| &n.data),
576 Some(NodeData::Comment { contents }) if contents == "note"
577 ));
578 }
579
580 #[test]
581 fn text_content_concatenates_the_subtree() {
582 let (mut doc, _html, _head, body) = skeleton();
583 let outer = div(&mut doc);
584 let inner = div(&mut doc);
585 let a = document::create_text_node(&mut doc, "one ").unwrap();
586 let b = document::create_text_node(&mut doc, "two").unwrap();
587 append_child(&mut doc, outer, a).unwrap();
588 append_child(&mut doc, inner, b).unwrap();
589 append_child(&mut doc, outer, inner).unwrap();
590 append_child(&mut doc, body, outer).unwrap();
591 assert_eq!(text_content(&doc, outer).unwrap(), "one two");
592 }
593
594 #[test]
601 fn text_content_into_agrees_with_text_content() {
602 let (mut doc, _html, _head, body) = skeleton();
603 let outer = div(&mut doc);
604 let inner = div(&mut doc);
605 let a = document::create_text_node(&mut doc, "one ").unwrap();
606 let b = document::create_text_node(&mut doc, "two").unwrap();
607 append_child(&mut doc, outer, a).unwrap();
608 append_child(&mut doc, inner, b).unwrap();
609 append_child(&mut doc, outer, inner).unwrap();
610 append_child(&mut doc, body, outer).unwrap();
611
612 let owned = text_content(&doc, outer).unwrap();
613 assert_eq!(text_content_len(&doc, outer).unwrap(), owned.len());
614
615 let mut buf = [0u8; 32];
616 assert_eq!(
617 text_content_into(&doc, outer, &mut buf).unwrap(),
618 owned.len()
619 );
620 assert_eq!(&buf[..owned.len()], owned.as_bytes());
621
622 let empty = div(&mut doc);
625 append_child(&mut doc, body, empty).unwrap();
626 assert_eq!(text_content_into(&doc, empty, &mut buf).unwrap(), 0);
627 }
628
629 #[test]
636 fn text_content_into_writes_nothing_when_it_does_not_fit() {
637 let (mut doc, _html, _head, body) = skeleton();
638 let outer = div(&mut doc);
639 let a = document::create_text_node(&mut doc, "one ").unwrap();
640 let b = document::create_text_node(&mut doc, "two").unwrap();
641 append_child(&mut doc, outer, a).unwrap();
642 append_child(&mut doc, outer, b).unwrap();
643 append_child(&mut doc, body, outer).unwrap();
644
645 let mut buf = [b'.'; 5];
646 assert_eq!(text_content_into(&doc, outer, &mut buf).unwrap(), 7);
647 assert_eq!(
648 &buf, b".....",
649 "a partial write would have left `one ` here"
650 );
651 }
652
653 #[test]
654 fn set_text_content_replaces_children_with_one_text_node() {
655 let (mut doc, _html, _head, body) = skeleton();
656 let outer = div(&mut doc);
657 let old_child = div(&mut doc);
658 append_child(&mut doc, outer, old_child).unwrap();
659 append_child(&mut doc, body, outer).unwrap();
660
661 set_text_content(&mut doc, outer, "replaced").unwrap();
662 assert_eq!(child_nodes(&doc, outer).unwrap().len(), 1);
663 assert_eq!(text_content(&doc, outer).unwrap(), "replaced");
664 assert!(doc.get_node(old_child).is_some());
666 assert_eq!(parent_node(&doc, old_child).unwrap(), None);
667 }
668
669 #[test]
670 fn set_text_content_empty_leaves_no_children() {
671 let (mut doc, _html, _head, body) = skeleton();
672 let outer = div(&mut doc);
673 append_child(&mut doc, body, outer).unwrap();
674 set_text_content(&mut doc, outer, "x").unwrap();
675 set_text_content(&mut doc, outer, "").unwrap();
676 assert!(child_nodes(&doc, outer).unwrap().is_empty());
677 }
678
679 #[test]
680 fn set_text_content_on_a_text_node_rewrites_it_in_place() {
681 let (mut doc, _html, _head, _body) = skeleton();
682 let text = document::create_text_node(&mut doc, "before").unwrap();
683 set_text_content(&mut doc, text, "after").unwrap();
684 assert_eq!(text_content(&doc, text).unwrap(), "after");
685 assert!(child_nodes(&doc, text).unwrap().is_empty());
686 }
687
688 #[test]
689 fn compare_document_position_reports_order_and_containment() {
690 use document_position::*;
691 let (mut doc, _html, _head, body) = skeleton();
692 let first = div(&mut doc);
693 let second = div(&mut doc);
694 let nested = div(&mut doc);
695 append_child(&mut doc, body, first).unwrap();
696 append_child(&mut doc, body, second).unwrap();
697 append_child(&mut doc, first, nested).unwrap();
698
699 assert_eq!(compare_document_position(&doc, first, first).unwrap(), 0);
700 assert_eq!(
701 compare_document_position(&doc, first, second).unwrap(),
702 FOLLOWING
703 );
704 assert_eq!(
705 compare_document_position(&doc, second, first).unwrap(),
706 PRECEDING
707 );
708 assert_eq!(
709 compare_document_position(&doc, first, nested).unwrap(),
710 FOLLOWING | CONTAINED_BY
711 );
712 assert_eq!(
713 compare_document_position(&doc, nested, first).unwrap(),
714 PRECEDING | CONTAINS
715 );
716 }
717
718 #[test]
719 fn compare_document_position_flags_disconnected_nodes() {
720 use document_position::*;
721 let (mut doc, _html, _head, body) = skeleton();
722 let attached = div(&mut doc);
723 let detached = div(&mut doc);
724 append_child(&mut doc, body, attached).unwrap();
725 let result = compare_document_position(&doc, attached, detached).unwrap();
726 assert_eq!(result & DISCONNECTED, DISCONNECTED);
727 assert_eq!(result & IMPLEMENTATION_SPECIFIC, IMPLEMENTATION_SPECIFIC);
728 assert_ne!(result & (PRECEDING | FOLLOWING), 0);
729 }
730}