1use std::collections::HashSet;
9
10use crate::css_selectors::Element;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub struct NodeId(pub(crate) usize);
20
21impl NodeId {
22 pub const DOCUMENT: NodeId = NodeId(0);
24
25 #[must_use]
27 pub fn from_raw(v: u32) -> Self {
28 Self(v as usize)
29 }
30
31 #[must_use]
33 pub fn to_raw(self) -> u32 {
34 self.0 as u32
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct QualName {
45 pub ns: Option<String>,
46 pub local: String,
47}
48
49impl QualName {
50 pub fn new(local: impl Into<String>) -> Self {
51 Self {
52 ns: None,
53 local: local.into(),
54 }
55 }
56
57 pub fn with_ns(ns: impl Into<String>, local: impl Into<String>) -> Self {
58 Self {
59 ns: Some(ns.into()),
60 local: local.into(),
61 }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct Attribute {
68 pub name: QualName,
69 pub value: String,
70}
71
72#[derive(Debug, Clone)]
78pub enum NodeData {
79 Document,
80 DocumentType {
81 name: String,
82 public_id: String,
83 system_id: String,
84 },
85 Element(ElementData),
86 Text(String),
87 Comment(String),
88 ProcessingInstruction {
89 target: String,
90 data: String,
91 },
92 DocumentFragment,
93 ShadowRoot {
94 mode: ShadowRootMode,
95 host: NodeId,
96 },
97}
98
99#[derive(Debug, Clone)]
100pub struct ElementData {
101 pub name: QualName,
102 pub attrs: Vec<Attribute>,
103 pub shadow_root: Option<NodeId>,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum ShadowRootMode {
108 Open,
109 Closed,
110}
111
112#[derive(Debug, Clone)]
114pub struct Node {
115 pub id: NodeId,
116 pub data: NodeData,
117 pub parent: Option<NodeId>,
118 pub first_child: Option<NodeId>,
119 pub last_child: Option<NodeId>,
120 pub prev_sibling: Option<NodeId>,
121 pub next_sibling: Option<NodeId>,
122}
123
124impl Node {
125 pub fn new(id: NodeId, data: NodeData) -> Self {
126 Self {
127 id,
128 data,
129 parent: None,
130 first_child: None,
131 last_child: None,
132 prev_sibling: None,
133 next_sibling: None,
134 }
135 }
136
137 pub fn is_element(&self) -> bool {
138 matches!(self.data, NodeData::Element(_))
139 }
140
141 pub fn as_element(&self) -> Option<&ElementData> {
142 match &self.data {
143 NodeData::Element(data) => Some(data),
144 _ => None,
145 }
146 }
147
148 pub fn as_element_mut(&mut self) -> Option<&mut ElementData> {
149 match &mut self.data {
150 NodeData::Element(data) => Some(data),
151 _ => None,
152 }
153 }
154
155 pub fn as_text(&self) -> Option<&str> {
156 match &self.data {
157 NodeData::Text(t) => Some(t),
158 _ => None,
159 }
160 }
161}
162
163#[derive(Debug)]
169pub struct Dom {
170 nodes: Vec<Option<Node>>,
171 free_list: Vec<usize>,
172}
173
174const WALK_LIMIT: usize = 2_000_000;
177
178const ANCESTOR_LIMIT: usize = 10_000;
180
181impl Dom {
182 pub fn new() -> Self {
184 let doc_node = Node::new(NodeId::DOCUMENT, NodeData::Document);
185 Self {
186 nodes: vec![Some(doc_node)],
187 free_list: Vec::new(),
188 }
189 }
190
191 pub fn document(&self) -> NodeId {
193 NodeId::DOCUMENT
194 }
195
196 pub fn get(&self, id: NodeId) -> Option<&Node> {
198 self.nodes.get(id.0).and_then(|n| n.as_ref())
199 }
200
201 pub fn get_mut(&mut self, id: NodeId) -> Option<&mut Node> {
203 self.nodes.get_mut(id.0).and_then(|n| n.as_mut())
204 }
205
206 pub fn len(&self) -> usize {
208 self.nodes.iter().filter(|n| n.is_some()).count()
209 }
210
211 pub fn is_empty(&self) -> bool {
212 self.len() == 0
213 }
214
215 fn allocate(&mut self, data: NodeData) -> NodeId {
218 if let Some(idx) = self.free_list.pop() {
219 let id = NodeId(idx);
220 self.nodes[idx] = Some(Node::new(id, data));
221 id
222 } else {
223 let id = NodeId(self.nodes.len());
224 self.nodes.push(Some(Node::new(id, data)));
225 id
226 }
227 }
228
229 pub fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) -> NodeId {
230 self.allocate(NodeData::Element(ElementData {
231 name,
232 attrs,
233 shadow_root: None,
234 }))
235 }
236
237 pub fn create_text(&mut self, text: String) -> NodeId {
238 self.allocate(NodeData::Text(text))
239 }
240
241 pub fn create_comment(&mut self, text: String) -> NodeId {
242 self.allocate(NodeData::Comment(text))
243 }
244
245 pub fn create_document_fragment(&mut self) -> NodeId {
246 self.allocate(NodeData::DocumentFragment)
247 }
248
249 pub fn create_shadow_root(&mut self, host: NodeId, mode: ShadowRootMode) -> NodeId {
251 let shadow_id = self.allocate(NodeData::ShadowRoot { mode, host });
252 if let Some(node) = self.get_mut(host) {
253 if let Some(elem) = node.as_element_mut() {
254 elem.shadow_root = Some(shadow_id);
255 }
256 }
257 shadow_id
258 }
259
260 pub fn allocate_pi(&mut self, target: String, data: String) -> NodeId {
261 self.allocate(NodeData::ProcessingInstruction { target, data })
262 }
263
264 pub fn create_doctype(&mut self, name: String, public_id: String, system_id: String) -> NodeId {
265 self.allocate(NodeData::DocumentType {
266 name,
267 public_id,
268 system_id,
269 })
270 }
271
272 fn is_ancestor_or_self(&self, candidate: NodeId, target: NodeId) -> bool {
276 if candidate == target {
277 return true;
278 }
279 let mut current = self.get(target).and_then(|n| n.parent);
280 let mut depth = 0usize;
281 while let Some(id) = current {
282 if id == candidate {
283 return true;
284 }
285 depth += 1;
286 if depth > ANCESTOR_LIMIT {
287 break;
288 }
289 current = self.get(id).and_then(|n| n.parent);
290 }
291 false
292 }
293
294 pub fn append_child(&mut self, parent: NodeId, child: NodeId) {
298 if self.get(parent).is_none() || self.get(child).is_none() {
299 return;
300 }
301 if self.is_ancestor_or_self(child, parent) {
302 return;
303 }
304 self.detach(child);
305
306 let old_last = self.get(parent).and_then(|n| n.last_child);
307
308 if let Some(node) = self.get_mut(child) {
309 node.parent = Some(parent);
310 node.prev_sibling = old_last;
311 node.next_sibling = None;
312 }
313
314 if let Some(old_last_id) = old_last {
315 if let Some(node) = self.get_mut(old_last_id) {
316 node.next_sibling = Some(child);
317 }
318 } else if let Some(node) = self.get_mut(parent) {
319 node.first_child = Some(child);
320 }
321
322 if let Some(node) = self.get_mut(parent) {
323 node.last_child = Some(child);
324 }
325 }
326
327 pub fn insert_before(&mut self, parent: NodeId, child: NodeId, reference: NodeId) {
329 if self.get(parent).is_none() || self.get(child).is_none() || self.get(reference).is_none()
330 {
331 return;
332 }
333 if self.is_ancestor_or_self(child, parent) {
334 return;
335 }
336 self.detach(child);
337
338 let prev = self.get(reference).and_then(|n| n.prev_sibling);
339
340 if let Some(node) = self.get_mut(child) {
341 node.parent = Some(parent);
342 node.prev_sibling = prev;
343 node.next_sibling = Some(reference);
344 }
345
346 if let Some(node) = self.get_mut(reference) {
347 node.prev_sibling = Some(child);
348 }
349
350 if let Some(prev_id) = prev {
351 if let Some(node) = self.get_mut(prev_id) {
352 node.next_sibling = Some(child);
353 }
354 } else if let Some(node) = self.get_mut(parent) {
355 node.first_child = Some(child);
356 }
357 }
358
359 pub fn detach(&mut self, id: NodeId) {
361 let (parent, prev, next) = match self.get(id) {
362 Some(node) => (node.parent, node.prev_sibling, node.next_sibling),
363 None => return,
364 };
365
366 if let Some(prev_id) = prev {
367 if let Some(node) = self.get_mut(prev_id) {
368 node.next_sibling = next;
369 }
370 } else if let Some(parent_id) = parent {
371 if let Some(node) = self.get_mut(parent_id) {
372 node.first_child = next;
373 }
374 }
375
376 if let Some(next_id) = next {
377 if let Some(node) = self.get_mut(next_id) {
378 node.prev_sibling = prev;
379 }
380 } else if let Some(parent_id) = parent {
381 if let Some(node) = self.get_mut(parent_id) {
382 node.last_child = prev;
383 }
384 }
385
386 if let Some(node) = self.get_mut(id) {
387 node.parent = None;
388 node.prev_sibling = None;
389 node.next_sibling = None;
390 }
391 }
392
393 pub fn remove(&mut self, id: NodeId) {
395 self.detach(id);
396 if id.0 < self.nodes.len() {
397 self.nodes[id.0] = None;
398 self.free_list.push(id.0);
399 }
400 }
401
402 pub fn reparent_children(&mut self, source: NodeId, target: NodeId) {
404 loop {
405 let child = self.get(source).and_then(|n| n.first_child);
406 match child {
407 Some(child_id) => self.append_child(target, child_id),
408 None => break,
409 }
410 }
411 }
412
413 pub fn children(&self, parent: NodeId) -> Vec<NodeId> {
417 let mut result = Vec::new();
418 let mut current = self.get(parent).and_then(|n| n.first_child);
419 while let Some(id) = current {
420 result.push(id);
421 current = self.get(id).and_then(|n| n.next_sibling);
422 }
423 result
424 }
425
426 pub fn child_elements(&self, parent: NodeId) -> Vec<NodeId> {
428 self.children(parent)
429 .into_iter()
430 .filter(|id| self.get(*id).is_some_and(|n| n.is_element()))
431 .collect()
432 }
433
434 pub fn text_content(&self, id: NodeId) -> String {
436 let mut result = String::new();
437 self.collect_text(id, &mut result);
438 result
439 }
440
441 fn collect_text(&self, root: NodeId, result: &mut String) {
443 let mut stack: Vec<NodeId> = vec![root];
444 let mut visited: HashSet<NodeId> = HashSet::with_capacity(64);
445 let mut steps: usize = 0;
446 while let Some(id) = stack.pop() {
447 if !visited.insert(id) {
448 continue;
449 }
450 steps += 1;
451 if steps > WALK_LIMIT {
452 break;
453 }
454 let node = match self.get(id) {
455 Some(n) => n,
456 None => continue,
457 };
458 match &node.data {
459 NodeData::Text(t) => result.push_str(t),
460 _ => {
461 let mut kids: Vec<NodeId> = Vec::new();
462 let mut child = node.first_child;
463 while let Some(c) = child {
464 kids.push(c);
465 child = self.get(c).and_then(|n| n.next_sibling);
466 }
467 stack.extend(kids.into_iter().rev());
468 }
469 }
470 }
471 }
472
473 pub fn set_text_content(&mut self, id: NodeId, text: &str) {
475 let children: Vec<NodeId> = self.children(id);
476 for child in children {
477 self.remove(child);
478 }
479 if !text.is_empty() {
480 let text_id = self.create_text(text.to_string());
481 self.append_child(id, text_id);
482 }
483 }
484
485 pub fn get_element_by_id(&self, id_value: &str) -> Option<NodeId> {
487 self.find_element(NodeId::DOCUMENT, &|node| {
488 node.as_element()
489 .and_then(|e| e.attrs.iter().find(|a| a.name.local == "id"))
490 .is_some_and(|a| a.value == id_value)
491 })
492 }
493
494 pub fn get_elements_by_tag_name(&self, root: NodeId, tag: &str) -> Vec<NodeId> {
496 let mut results = Vec::new();
497 self.collect_elements(
498 root,
499 &|node| {
500 node.as_element()
501 .is_some_and(|e| e.name.local.eq_ignore_ascii_case(tag))
502 },
503 &mut results,
504 );
505 results
506 }
507
508 pub fn get_elements_by_class_name(&self, root: NodeId, class: &str) -> Vec<NodeId> {
510 let mut results = Vec::new();
511 self.collect_elements(
512 root,
513 &|node| {
514 node.as_element()
515 .and_then(|e| e.attrs.iter().find(|a| a.name.local == "class"))
516 .is_some_and(|a| a.value.split_whitespace().any(|c| c == class))
517 },
518 &mut results,
519 );
520 results
521 }
522
523 pub fn serialize_html(&self, id: NodeId) -> String {
525 let mut out = String::new();
526 self.serialize_node(id, &mut out);
527 out
528 }
529
530 pub fn serialize_inner_html(&self, id: NodeId) -> String {
532 let mut out = String::new();
533 let mut kids: Vec<NodeId> = Vec::new();
534 let mut child = self.get(id).and_then(|n| n.first_child);
535 while let Some(child_id) = child {
536 kids.push(child_id);
537 child = self.get(child_id).and_then(|n| n.next_sibling);
538 }
539 for c in kids {
540 self.serialize_node(c, &mut out);
541 }
542 out
543 }
544
545 fn serialize_node(&self, root: NodeId, out: &mut String) {
547 enum SerWork {
548 Open(NodeId),
549 Close(String),
550 }
551 const VOID_ELEMENTS: &[&str] = &[
552 "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param",
553 "source", "track", "wbr",
554 ];
555
556 let mut stack: Vec<SerWork> = vec![SerWork::Open(root)];
557 let mut visited: HashSet<NodeId> = HashSet::with_capacity(64);
558 let mut steps: usize = 0;
559 while let Some(work) = stack.pop() {
560 match work {
561 SerWork::Close(s) => out.push_str(&s),
562 SerWork::Open(id) => {
563 if !visited.insert(id) {
564 continue;
565 }
566 steps += 1;
567 if steps > WALK_LIMIT {
568 break;
569 }
570 let node = match self.get(id) {
571 Some(n) => n,
572 None => continue,
573 };
574 match &node.data {
575 NodeData::Element(elem) => {
576 out.push('<');
577 out.push_str(&elem.name.local);
578 for attr in &elem.attrs {
579 out.push(' ');
580 out.push_str(&attr.name.local);
581 out.push_str("=\"");
582 out.push_str(
583 &attr.value.replace('&', "&").replace('"', """),
584 );
585 out.push('"');
586 }
587 out.push('>');
588
589 let is_void = VOID_ELEMENTS.contains(&elem.name.local.as_str());
590 if !is_void {
591 stack.push(SerWork::Close(format!("</{}>", elem.name.local)));
592 }
593 let mut kids: Vec<NodeId> = Vec::new();
594 let mut child = node.first_child;
595 while let Some(c) = child {
596 kids.push(c);
597 child = self.get(c).and_then(|n| n.next_sibling);
598 }
599 for c in kids.into_iter().rev() {
600 stack.push(SerWork::Open(c));
601 }
602 }
603 NodeData::Text(text) => {
604 out.push_str(
605 &text
606 .replace('&', "&")
607 .replace('<', "<")
608 .replace('>', ">"),
609 );
610 }
611 NodeData::Comment(text) => {
612 out.push_str("<!--");
613 out.push_str(text);
614 out.push_str("-->");
615 }
616 NodeData::DocumentType { name, .. } => {
617 out.push_str("<!DOCTYPE ");
618 out.push_str(name);
619 out.push('>');
620 }
621 NodeData::Document | NodeData::DocumentFragment => {
622 let mut kids: Vec<NodeId> = Vec::new();
623 let mut child = node.first_child;
624 while let Some(c) = child {
625 kids.push(c);
626 child = self.get(c).and_then(|n| n.next_sibling);
627 }
628 for c in kids.into_iter().rev() {
629 stack.push(SerWork::Open(c));
630 }
631 }
632 _ => {}
633 }
634 }
635 }
636 }
637 }
638
639 pub fn merge_subtree(&mut self, source: &Dom, source_root: NodeId) -> NodeId {
641 fn create_from(this: &mut Dom, source: &Dom, src_id: NodeId) -> Option<NodeId> {
642 let src = source.get(src_id)?;
643 Some(match &src.data {
644 NodeData::Element(elem) => {
645 this.create_element(elem.name.clone(), elem.attrs.clone())
646 }
647 NodeData::Text(t) => this.create_text(t.clone()),
648 NodeData::Comment(t) => this.create_comment(t.clone()),
649 NodeData::DocumentFragment | NodeData::Document => this.create_document_fragment(),
650 _ => this.create_document_fragment(),
651 })
652 }
653
654 let new_root = match create_from(self, source, source_root) {
655 Some(id) => id,
656 None => return self.create_document_fragment(),
657 };
658
659 let mut queue: Vec<(NodeId, NodeId)> = Vec::new();
660 let mut visited: HashSet<NodeId> = HashSet::with_capacity(64);
661 visited.insert(source_root);
662
663 let mut child = source.get(source_root).and_then(|n| n.first_child);
664 while let Some(c) = child {
665 queue.push((c, new_root));
666 child = source.get(c).and_then(|n| n.next_sibling);
667 }
668
669 let mut steps: usize = 0;
670 let mut i = 0usize;
671 while i < queue.len() {
672 let (src_id, dest_parent) = queue[i];
673 i += 1;
674 steps += 1;
675 if steps > WALK_LIMIT {
676 break;
677 }
678 if !visited.insert(src_id) {
679 continue;
680 }
681 let new_id = match create_from(self, source, src_id) {
682 Some(id) => id,
683 None => continue,
684 };
685 self.append_child(dest_parent, new_id);
686 let mut child = source.get(src_id).and_then(|n| n.first_child);
687 while let Some(c) = child {
688 queue.push((c, new_id));
689 child = source.get(c).and_then(|n| n.next_sibling);
690 }
691 }
692
693 new_root
694 }
695
696 pub fn node_type(&self, id: NodeId) -> u32 {
698 match self.get(id).map(|n| &n.data) {
699 Some(NodeData::Element(_)) => 1,
700 Some(NodeData::Text(_)) => 3,
701 Some(NodeData::ProcessingInstruction { .. }) => 7,
702 Some(NodeData::Comment(_)) => 8,
703 Some(NodeData::Document) => 9,
704 Some(NodeData::DocumentType { .. }) => 10,
705 Some(NodeData::DocumentFragment) => 11,
706 Some(NodeData::ShadowRoot { .. }) => 11,
707 None => 0,
708 }
709 }
710
711 fn find_element(&self, root: NodeId, predicate: &dyn Fn(&Node) -> bool) -> Option<NodeId> {
715 let mut stack: Vec<NodeId> = Vec::new();
716 let mut child = self.get(root).and_then(|n| n.first_child);
717 let mut seed: Vec<NodeId> = Vec::new();
718 while let Some(c) = child {
719 seed.push(c);
720 child = self.get(c).and_then(|n| n.next_sibling);
721 }
722 stack.extend(seed.into_iter().rev());
723
724 let mut visited: HashSet<NodeId> = HashSet::with_capacity(64);
725 let mut steps: usize = 0;
726 while let Some(id) = stack.pop() {
727 if !visited.insert(id) {
728 continue;
729 }
730 steps += 1;
731 if steps > WALK_LIMIT {
732 break;
733 }
734 let node = match self.get(id) {
735 Some(n) => n,
736 None => continue,
737 };
738 if predicate(node) {
739 return Some(id);
740 }
741 let mut kids: Vec<NodeId> = Vec::new();
742 let mut child = node.first_child;
743 while let Some(c) = child {
744 kids.push(c);
745 child = self.get(c).and_then(|n| n.next_sibling);
746 }
747 stack.extend(kids.into_iter().rev());
748 }
749 None
750 }
751
752 fn collect_elements(
754 &self,
755 root: NodeId,
756 predicate: &dyn Fn(&Node) -> bool,
757 results: &mut Vec<NodeId>,
758 ) {
759 let mut stack: Vec<NodeId> = Vec::new();
760 let mut seed: Vec<NodeId> = Vec::new();
761 let mut child = self.get(root).and_then(|n| n.first_child);
762 while let Some(c) = child {
763 seed.push(c);
764 child = self.get(c).and_then(|n| n.next_sibling);
765 }
766 stack.extend(seed.into_iter().rev());
767
768 let mut visited: HashSet<NodeId> = HashSet::with_capacity(64);
769 let mut steps: usize = 0;
770 while let Some(id) = stack.pop() {
771 if !visited.insert(id) {
772 continue;
773 }
774 steps += 1;
775 if steps > WALK_LIMIT {
776 break;
777 }
778 let node = match self.get(id) {
779 Some(n) => n,
780 None => continue,
781 };
782 if predicate(node) {
783 results.push(id);
784 }
785 let mut kids: Vec<NodeId> = Vec::new();
786 let mut child = node.first_child;
787 while let Some(c) = child {
788 kids.push(c);
789 child = self.get(c).and_then(|n| n.next_sibling);
790 }
791 stack.extend(kids.into_iter().rev());
792 }
793 }
794}
795
796impl Default for Dom {
797 fn default() -> Self {
798 Self::new()
799 }
800}
801
802#[derive(Clone)]
811pub struct DomElement<'a> {
812 pub dom: &'a Dom,
813 pub id: NodeId,
814}
815
816impl<'a> DomElement<'a> {
817 pub fn new(dom: &'a Dom, id: NodeId) -> Option<Self> {
818 let node = dom.get(id)?;
819 node.is_element().then_some(Self { dom, id })
820 }
821
822 pub fn node_id(&self) -> NodeId {
823 self.id
824 }
825
826 fn node(&self) -> &Node {
827 match self.dom.get(self.id) {
829 Some(n) => n,
830 None => unreachable!(
831 "DomElement::node invariant: node must exist (validated in constructor)"
832 ),
833 }
834 }
835
836 fn element_data(&self) -> &ElementData {
837 match self.node().as_element() {
839 Some(d) => d,
840 None => unreachable!(
841 "DomElement::element_data invariant: node must be element (validated in constructor)"
842 ),
843 }
844 }
845}
846
847impl<'a> std::fmt::Debug for DomElement<'a> {
848 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
849 let data = self.element_data();
850 write!(f, "<{}", data.name.local)?;
851 for attr in &data.attrs {
852 write!(f, " {}=\"{}\"", attr.name.local, attr.value)?;
853 }
854 write!(f, ">")
855 }
856}
857
858impl<'a> Element for DomElement<'a> {
859 fn local_name(&self) -> &str {
860 &self.element_data().name.local
861 }
862
863 fn namespace(&self) -> Option<&str> {
864 self.element_data().name.ns.as_deref()
865 }
866
867 fn id(&self) -> Option<&str> {
868 self.element_data()
869 .attrs
870 .iter()
871 .find(|a| a.name.local == "id")
872 .map(|a| a.value.as_str())
873 }
874
875 fn has_class(&self, name: &str) -> bool {
876 self.element_data()
877 .attrs
878 .iter()
879 .find(|a| a.name.local == "class")
880 .is_some_and(|a| a.value.split_whitespace().any(|c| c == name))
881 }
882
883 fn has_attribute(&self, name: &str) -> bool {
884 self.element_data()
885 .attrs
886 .iter()
887 .any(|a| a.name.local.eq_ignore_ascii_case(name))
888 }
889
890 fn attribute_value(&self, name: &str) -> Option<&str> {
891 self.element_data()
892 .attrs
893 .iter()
894 .find(|a| a.name.local.eq_ignore_ascii_case(name))
895 .map(|a| a.value.as_str())
896 }
897
898 fn parent_element(&self) -> Option<Self> {
899 let mut parent_id = self.node().parent?;
900 loop {
901 let parent = self.dom.get(parent_id)?;
902 if parent.is_element() {
903 return Some(DomElement {
904 dom: self.dom,
905 id: parent_id,
906 });
907 }
908 parent_id = parent.parent?;
909 }
910 }
911
912 fn prev_sibling_element(&self) -> Option<Self> {
913 let mut sib_id = self.node().prev_sibling?;
914 loop {
915 let sib = self.dom.get(sib_id)?;
916 if sib.is_element() {
917 return Some(DomElement {
918 dom: self.dom,
919 id: sib_id,
920 });
921 }
922 sib_id = sib.prev_sibling?;
923 }
924 }
925
926 fn next_sibling_element(&self) -> Option<Self> {
927 let mut sib_id = self.node().next_sibling?;
928 loop {
929 let sib = self.dom.get(sib_id)?;
930 if sib.is_element() {
931 return Some(DomElement {
932 dom: self.dom,
933 id: sib_id,
934 });
935 }
936 sib_id = sib.next_sibling?;
937 }
938 }
939
940 fn first_child_element(&self) -> Option<Self> {
941 let mut child_id = self.node().first_child?;
942 loop {
943 let child = self.dom.get(child_id)?;
944 if child.is_element() {
945 return Some(DomElement {
946 dom: self.dom,
947 id: child_id,
948 });
949 }
950 child_id = child.next_sibling?;
951 }
952 }
953
954 fn last_child_element(&self) -> Option<Self> {
955 let mut child_id = self.node().last_child?;
956 loop {
957 let child = self.dom.get(child_id)?;
958 if child.is_element() {
959 return Some(DomElement {
960 dom: self.dom,
961 id: child_id,
962 });
963 }
964 child_id = child.prev_sibling?;
965 }
966 }
967
968 fn is_root(&self) -> bool {
969 match self.node().parent {
970 Some(parent_id) => self
971 .dom
972 .get(parent_id)
973 .is_some_and(|n| matches!(n.data, NodeData::Document)),
974 None => false,
975 }
976 }
977
978 fn is_empty(&self) -> bool {
979 let mut child_id = self.node().first_child;
980 while let Some(id) = child_id {
981 if let Some(child) = self.dom.get(id) {
982 match &child.data {
983 NodeData::Element(_) => return false,
984 NodeData::Text(t) if !t.is_empty() => return false,
985 _ => {}
986 }
987 child_id = child.next_sibling;
988 } else {
989 break;
990 }
991 }
992 true
993 }
994
995 fn is_link(&self) -> bool {
996 let name = self.local_name();
997 (name == "a" || name == "area") && self.has_attribute("href")
998 }
999}
1000
1001#[cfg(test)]
1006mod tests {
1007 use super::*;
1008
1009 fn build_test_dom() -> Dom {
1010 let mut dom = Dom::new();
1011 let html = dom.create_element(QualName::new("html"), vec![]);
1012 dom.append_child(NodeId::DOCUMENT, html);
1013
1014 let body = dom.create_element(QualName::new("body"), vec![]);
1015 dom.append_child(html, body);
1016
1017 let div = dom.create_element(
1018 QualName::new("div"),
1019 vec![
1020 Attribute {
1021 name: QualName::new("id"),
1022 value: "main".to_string(),
1023 },
1024 Attribute {
1025 name: QualName::new("class"),
1026 value: "container active".to_string(),
1027 },
1028 ],
1029 );
1030 dom.append_child(body, div);
1031
1032 let p = dom.create_element(QualName::new("p"), vec![]);
1033 dom.append_child(div, p);
1034
1035 let text = dom.create_text("Hello world".to_string());
1036 dom.append_child(p, text);
1037
1038 dom
1039 }
1040
1041 #[test]
1042 fn new_dom_has_document() {
1043 let dom = Dom::new();
1044 assert_eq!(dom.len(), 1);
1045 assert!(dom.get(NodeId::DOCUMENT).is_some());
1046 }
1047
1048 #[test]
1049 fn create_and_append() {
1050 let mut dom = Dom::new();
1051 let div = dom.create_element(QualName::new("div"), vec![]);
1052 dom.append_child(NodeId::DOCUMENT, div);
1053
1054 assert_eq!(dom.children(NodeId::DOCUMENT), vec![div]);
1055 assert_eq!(dom.get(div).unwrap().parent, Some(NodeId::DOCUMENT));
1056 }
1057
1058 #[test]
1059 fn append_multiple_children() {
1060 let mut dom = Dom::new();
1061 let a = dom.create_element(QualName::new("a"), vec![]);
1062 let b = dom.create_element(QualName::new("b"), vec![]);
1063 let c = dom.create_element(QualName::new("c"), vec![]);
1064 dom.append_child(NodeId::DOCUMENT, a);
1065 dom.append_child(NodeId::DOCUMENT, b);
1066 dom.append_child(NodeId::DOCUMENT, c);
1067
1068 assert_eq!(dom.children(NodeId::DOCUMENT), vec![a, b, c]);
1069 assert_eq!(dom.get(a).unwrap().next_sibling, Some(b));
1070 assert_eq!(dom.get(b).unwrap().prev_sibling, Some(a));
1071 assert_eq!(dom.get(b).unwrap().next_sibling, Some(c));
1072 }
1073
1074 #[test]
1075 fn insert_before_test() {
1076 let mut dom = Dom::new();
1077 let a = dom.create_element(QualName::new("a"), vec![]);
1078 let c = dom.create_element(QualName::new("c"), vec![]);
1079 dom.append_child(NodeId::DOCUMENT, a);
1080 dom.append_child(NodeId::DOCUMENT, c);
1081
1082 let b = dom.create_element(QualName::new("b"), vec![]);
1083 dom.insert_before(NodeId::DOCUMENT, b, c);
1084
1085 assert_eq!(dom.children(NodeId::DOCUMENT), vec![a, b, c]);
1086 }
1087
1088 #[test]
1089 fn detach_test() {
1090 let mut dom = Dom::new();
1091 let a = dom.create_element(QualName::new("a"), vec![]);
1092 let b = dom.create_element(QualName::new("b"), vec![]);
1093 let c = dom.create_element(QualName::new("c"), vec![]);
1094 dom.append_child(NodeId::DOCUMENT, a);
1095 dom.append_child(NodeId::DOCUMENT, b);
1096 dom.append_child(NodeId::DOCUMENT, c);
1097
1098 dom.detach(b);
1099 assert_eq!(dom.children(NodeId::DOCUMENT), vec![a, c]);
1100 assert_eq!(dom.get(a).unwrap().next_sibling, Some(c));
1101 assert_eq!(dom.get(c).unwrap().prev_sibling, Some(a));
1102 }
1103
1104 #[test]
1105 fn remove_recycles() {
1106 let mut dom = Dom::new();
1107 let a = dom.create_element(QualName::new("a"), vec![]);
1108 dom.append_child(NodeId::DOCUMENT, a);
1109 dom.remove(a);
1110
1111 let b = dom.create_element(QualName::new("b"), vec![]);
1112 assert_eq!(b.0, a.0);
1113 }
1114
1115 #[test]
1116 fn text_content_test() {
1117 let mut dom = Dom::new();
1118 let div = dom.create_element(QualName::new("div"), vec![]);
1119 let text1 = dom.create_text("Hello ".to_string());
1120 let span = dom.create_element(QualName::new("span"), vec![]);
1121 let text2 = dom.create_text("world".to_string());
1122
1123 dom.append_child(NodeId::DOCUMENT, div);
1124 dom.append_child(div, text1);
1125 dom.append_child(div, span);
1126 dom.append_child(span, text2);
1127
1128 assert_eq!(dom.text_content(div), "Hello world");
1129 }
1130
1131 #[test]
1132 fn child_elements_skips_text() {
1133 let mut dom = Dom::new();
1134 let div = dom.create_element(QualName::new("div"), vec![]);
1135 let text = dom.create_text("hello".to_string());
1136 let span = dom.create_element(QualName::new("span"), vec![]);
1137
1138 dom.append_child(NodeId::DOCUMENT, div);
1139 dom.append_child(div, text);
1140 dom.append_child(div, span);
1141
1142 assert_eq!(dom.child_elements(div), vec![span]);
1143 }
1144
1145 #[test]
1146 fn reparent_children_test() {
1147 let mut dom = Dom::new();
1148 let source = dom.create_element(QualName::new("source"), vec![]);
1149 let target = dom.create_element(QualName::new("target"), vec![]);
1150 let a = dom.create_element(QualName::new("a"), vec![]);
1151 let b = dom.create_element(QualName::new("b"), vec![]);
1152
1153 dom.append_child(NodeId::DOCUMENT, source);
1154 dom.append_child(NodeId::DOCUMENT, target);
1155 dom.append_child(source, a);
1156 dom.append_child(source, b);
1157
1158 dom.reparent_children(source, target);
1159
1160 assert!(dom.children(source).is_empty());
1161 assert_eq!(dom.children(target), vec![a, b]);
1162 }
1163
1164 #[test]
1165 fn node_id_raw_roundtrip() {
1166 let id = NodeId::from_raw(42);
1167 assert_eq!(id.to_raw(), 42);
1168 }
1169
1170 #[test]
1171 fn set_text_content_test() {
1172 let mut dom = Dom::new();
1173 let div = dom.create_element(QualName::new("div"), vec![]);
1174 let span = dom.create_element(QualName::new("span"), vec![]);
1175 let text = dom.create_text("old".to_string());
1176 dom.append_child(NodeId::DOCUMENT, div);
1177 dom.append_child(div, span);
1178 dom.append_child(span, text);
1179
1180 dom.set_text_content(div, "new text");
1181 assert_eq!(dom.text_content(div), "new text");
1182 assert_eq!(dom.child_elements(div).len(), 0);
1183 }
1184
1185 #[test]
1186 fn get_element_by_id_test() {
1187 let mut dom = Dom::new();
1188 let div = dom.create_element(
1189 QualName::new("div"),
1190 vec![Attribute {
1191 name: QualName::new("id"),
1192 value: "main".to_string(),
1193 }],
1194 );
1195 dom.append_child(NodeId::DOCUMENT, div);
1196
1197 assert_eq!(dom.get_element_by_id("main"), Some(div));
1198 assert_eq!(dom.get_element_by_id("nonexistent"), None);
1199 }
1200
1201 #[test]
1202 fn get_elements_by_tag_name_test() {
1203 let mut dom = Dom::new();
1204 let div = dom.create_element(QualName::new("div"), vec![]);
1205 let p1 = dom.create_element(QualName::new("p"), vec![]);
1206 let p2 = dom.create_element(QualName::new("p"), vec![]);
1207 let span = dom.create_element(QualName::new("span"), vec![]);
1208 dom.append_child(NodeId::DOCUMENT, div);
1209 dom.append_child(div, p1);
1210 dom.append_child(div, span);
1211 dom.append_child(div, p2);
1212
1213 let ps = dom.get_elements_by_tag_name(NodeId::DOCUMENT, "p");
1214 assert_eq!(ps, vec![p1, p2]);
1215 }
1216
1217 #[test]
1218 fn get_elements_by_class_name_test() {
1219 let mut dom = Dom::new();
1220 let a = dom.create_element(
1221 QualName::new("div"),
1222 vec![Attribute {
1223 name: QualName::new("class"),
1224 value: "foo bar".to_string(),
1225 }],
1226 );
1227 let b = dom.create_element(
1228 QualName::new("div"),
1229 vec![Attribute {
1230 name: QualName::new("class"),
1231 value: "baz".to_string(),
1232 }],
1233 );
1234 dom.append_child(NodeId::DOCUMENT, a);
1235 dom.append_child(NodeId::DOCUMENT, b);
1236
1237 assert_eq!(
1238 dom.get_elements_by_class_name(NodeId::DOCUMENT, "foo"),
1239 vec![a]
1240 );
1241 assert_eq!(
1242 dom.get_elements_by_class_name(NodeId::DOCUMENT, "baz"),
1243 vec![b]
1244 );
1245 }
1246
1247 #[test]
1248 fn serialize_html_basic() {
1249 let mut dom = Dom::new();
1250 let div = dom.create_element(
1251 QualName::new("div"),
1252 vec![Attribute {
1253 name: QualName::new("id"),
1254 value: "main".to_string(),
1255 }],
1256 );
1257 let text = dom.create_text("Hello".to_string());
1258 dom.append_child(NodeId::DOCUMENT, div);
1259 dom.append_child(div, text);
1260
1261 assert_eq!(dom.serialize_html(div), "<div id=\"main\">Hello</div>");
1262 }
1263
1264 #[test]
1265 fn serialize_inner_html_test() {
1266 let mut dom = Dom::new();
1267 let div = dom.create_element(QualName::new("div"), vec![]);
1268 let span = dom.create_element(QualName::new("span"), vec![]);
1269 let text = dom.create_text("hi".to_string());
1270 dom.append_child(NodeId::DOCUMENT, div);
1271 dom.append_child(div, span);
1272 dom.append_child(span, text);
1273
1274 assert_eq!(dom.serialize_inner_html(div), "<span>hi</span>");
1275 }
1276
1277 #[test]
1278 fn merge_subtree_test() {
1279 let mut source = Dom::new();
1280 let div = source.create_element(QualName::new("div"), vec![]);
1281 let text = source.create_text("copied".to_string());
1282 source.append_child(NodeId::DOCUMENT, div);
1283 source.append_child(div, text);
1284
1285 let mut target = Dom::new();
1286 let parent = target.create_element(QualName::new("body"), vec![]);
1287 target.append_child(NodeId::DOCUMENT, parent);
1288
1289 let new_div = target.merge_subtree(&source, div);
1290 target.append_child(parent, new_div);
1291
1292 assert_eq!(target.text_content(new_div), "copied");
1293 assert_eq!(
1294 target
1295 .get(new_div)
1296 .and_then(|n| n.as_element())
1297 .map(|e| e.name.local.as_str()),
1298 Some("div")
1299 );
1300 }
1301
1302 #[test]
1303 fn append_child_rejects_self_cycle() {
1304 let mut dom = Dom::new();
1305 let div = dom.create_element(QualName::new("div"), vec![]);
1306 dom.append_child(NodeId::DOCUMENT, div);
1307 dom.append_child(div, div);
1308 assert_eq!(dom.children(NodeId::DOCUMENT), vec![div]);
1309 assert!(dom.children(div).is_empty());
1310 assert_eq!(dom.get(div).unwrap().parent, Some(NodeId::DOCUMENT));
1311 }
1312
1313 #[test]
1314 fn append_child_rejects_ancestor_cycle() {
1315 let mut dom = Dom::new();
1316 let outer = dom.create_element(QualName::new("outer"), vec![]);
1317 let inner = dom.create_element(QualName::new("inner"), vec![]);
1318 dom.append_child(NodeId::DOCUMENT, outer);
1319 dom.append_child(outer, inner);
1320 dom.append_child(inner, outer);
1321 assert_eq!(dom.children(NodeId::DOCUMENT), vec![outer]);
1322 assert_eq!(dom.children(outer), vec![inner]);
1323 assert!(dom.children(inner).is_empty());
1324 }
1325
1326 #[test]
1327 fn node_type_test() {
1328 let mut dom = Dom::new();
1329 assert_eq!(dom.node_type(NodeId::DOCUMENT), 9);
1330 let el = dom.create_element(QualName::new("div"), vec![]);
1331 assert_eq!(dom.node_type(el), 1);
1332 let text = dom.create_text("hi".to_string());
1333 assert_eq!(dom.node_type(text), 3);
1334 let comment = dom.create_comment("c".to_string());
1335 assert_eq!(dom.node_type(comment), 8);
1336 }
1337
1338 #[test]
1339 fn element_local_name() {
1340 let dom = build_test_dom();
1341 let html_el = DomElement::new(&dom, dom.children(NodeId::DOCUMENT)[0]).unwrap();
1342 assert_eq!(html_el.local_name(), "html");
1343 }
1344
1345 #[test]
1346 fn element_id_and_class() {
1347 let dom = build_test_dom();
1348 let html = dom.children(NodeId::DOCUMENT)[0];
1349 let body = dom.children(html)[0];
1350 let div = dom.children(body)[0];
1351 let el = DomElement::new(&dom, div).unwrap();
1352
1353 assert_eq!(el.id(), Some("main"));
1354 assert!(el.has_class("container"));
1355 assert!(el.has_class("active"));
1356 assert!(!el.has_class("inactive"));
1357 }
1358
1359 #[test]
1360 fn parent_and_children() {
1361 let dom = build_test_dom();
1362 let html = dom.children(NodeId::DOCUMENT)[0];
1363 let body = dom.children(html)[0];
1364 let div = dom.children(body)[0];
1365 let p = dom.children(div)[0];
1366
1367 let p_el = DomElement::new(&dom, p).unwrap();
1368 let parent = p_el.parent_element().unwrap();
1369 assert_eq!(parent.local_name(), "div");
1370
1371 let div_el = DomElement::new(&dom, div).unwrap();
1372 let first_child = div_el.first_child_element().unwrap();
1373 assert_eq!(first_child.local_name(), "p");
1374 }
1375
1376 #[test]
1377 fn is_root_test() {
1378 let dom = build_test_dom();
1379 let html = dom.children(NodeId::DOCUMENT)[0];
1380 let body = dom.children(html)[0];
1381
1382 let html_el = DomElement::new(&dom, html).unwrap();
1383 assert!(html_el.is_root());
1384
1385 let body_el = DomElement::new(&dom, body).unwrap();
1386 assert!(!body_el.is_root());
1387 }
1388
1389 #[test]
1390 fn is_empty_test() {
1391 let dom = build_test_dom();
1392 let html = dom.children(NodeId::DOCUMENT)[0];
1393 let body = dom.children(html)[0];
1394 let div = dom.children(body)[0];
1395
1396 let div_el = DomElement::new(&dom, div).unwrap();
1397 assert!(!div_el.is_empty());
1398 }
1399
1400 #[test]
1402 fn bdd_parse_simple_html() {
1403 let mut dom = Dom::new();
1404 let html = dom.create_element(QualName::new("html"), vec![]);
1405 let body = dom.create_element(QualName::new("body"), vec![]);
1406 let h1 = dom.create_element(QualName::new("h1"), vec![]);
1407 let text = dom.create_text("Hello".to_string());
1408
1409 dom.append_child(NodeId::DOCUMENT, html);
1410 dom.append_child(html, body);
1411 dom.append_child(body, h1);
1412 dom.append_child(h1, text);
1413
1414 assert!(dom.get(NodeId::DOCUMENT).is_some());
1415 let h1_children = dom.children(h1);
1416 assert_eq!(h1_children.len(), 1);
1417 assert_eq!(dom.text_content(h1), "Hello");
1418 }
1419
1420 #[test]
1422 fn bdd_query_elements_by_tag() {
1423 let mut dom = Dom::new();
1424 let div = dom.create_element(
1425 QualName::new("div"),
1426 vec![Attribute {
1427 name: QualName::new("class"),
1428 value: "content".to_string(),
1429 }],
1430 );
1431 let p1 = dom.create_element(QualName::new("p"), vec![]);
1432 let t1 = dom.create_text("First".to_string());
1433 let p2 = dom.create_element(QualName::new("p"), vec![]);
1434 let t2 = dom.create_text("Second".to_string());
1435
1436 dom.append_child(NodeId::DOCUMENT, div);
1437 dom.append_child(div, p1);
1438 dom.append_child(p1, t1);
1439 dom.append_child(div, p2);
1440 dom.append_child(p2, t2);
1441
1442 let ps = dom.get_elements_by_tag_name(NodeId::DOCUMENT, "p");
1443 assert_eq!(ps.len(), 2);
1444 assert_eq!(dom.text_content(ps[0]), "First");
1445 }
1446
1447 #[test]
1449 fn bdd_mutate_dom_tree() {
1450 let mut dom = Dom::new();
1451 let div = dom.create_element(QualName::new("div"), vec![]);
1452 let span = dom.create_element(QualName::new("span"), vec![]);
1453 let old_text = dom.create_text("Old".to_string());
1454
1455 dom.append_child(NodeId::DOCUMENT, div);
1456 dom.append_child(div, span);
1457 dom.append_child(span, old_text);
1458
1459 let p = dom.create_element(QualName::new("p"), vec![]);
1460 let new_text = dom.create_text("New".to_string());
1461 dom.append_child(div, p);
1462 dom.append_child(p, new_text);
1463
1464 assert_eq!(dom.children(div).len(), 2);
1465 }
1466}