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 }
832 }
833
834 fn element_data(&self) -> &ElementData {
835 match self.node().as_element() {
837 Some(d) => d,
838 None => unreachable!(),
839 }
840 }
841}
842
843impl<'a> std::fmt::Debug for DomElement<'a> {
844 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
845 let data = self.element_data();
846 write!(f, "<{}", data.name.local)?;
847 for attr in &data.attrs {
848 write!(f, " {}=\"{}\"", attr.name.local, attr.value)?;
849 }
850 write!(f, ">")
851 }
852}
853
854impl<'a> Element for DomElement<'a> {
855 fn local_name(&self) -> &str {
856 &self.element_data().name.local
857 }
858
859 fn namespace(&self) -> Option<&str> {
860 self.element_data().name.ns.as_deref()
861 }
862
863 fn id(&self) -> Option<&str> {
864 self.element_data()
865 .attrs
866 .iter()
867 .find(|a| a.name.local == "id")
868 .map(|a| a.value.as_str())
869 }
870
871 fn has_class(&self, name: &str) -> bool {
872 self.element_data()
873 .attrs
874 .iter()
875 .find(|a| a.name.local == "class")
876 .is_some_and(|a| a.value.split_whitespace().any(|c| c == name))
877 }
878
879 fn has_attribute(&self, name: &str) -> bool {
880 self.element_data()
881 .attrs
882 .iter()
883 .any(|a| a.name.local.eq_ignore_ascii_case(name))
884 }
885
886 fn attribute_value(&self, name: &str) -> Option<&str> {
887 self.element_data()
888 .attrs
889 .iter()
890 .find(|a| a.name.local.eq_ignore_ascii_case(name))
891 .map(|a| a.value.as_str())
892 }
893
894 fn parent_element(&self) -> Option<Self> {
895 let mut parent_id = self.node().parent?;
896 loop {
897 let parent = self.dom.get(parent_id)?;
898 if parent.is_element() {
899 return Some(DomElement {
900 dom: self.dom,
901 id: parent_id,
902 });
903 }
904 parent_id = parent.parent?;
905 }
906 }
907
908 fn prev_sibling_element(&self) -> Option<Self> {
909 let mut sib_id = self.node().prev_sibling?;
910 loop {
911 let sib = self.dom.get(sib_id)?;
912 if sib.is_element() {
913 return Some(DomElement {
914 dom: self.dom,
915 id: sib_id,
916 });
917 }
918 sib_id = sib.prev_sibling?;
919 }
920 }
921
922 fn next_sibling_element(&self) -> Option<Self> {
923 let mut sib_id = self.node().next_sibling?;
924 loop {
925 let sib = self.dom.get(sib_id)?;
926 if sib.is_element() {
927 return Some(DomElement {
928 dom: self.dom,
929 id: sib_id,
930 });
931 }
932 sib_id = sib.next_sibling?;
933 }
934 }
935
936 fn first_child_element(&self) -> Option<Self> {
937 let mut child_id = self.node().first_child?;
938 loop {
939 let child = self.dom.get(child_id)?;
940 if child.is_element() {
941 return Some(DomElement {
942 dom: self.dom,
943 id: child_id,
944 });
945 }
946 child_id = child.next_sibling?;
947 }
948 }
949
950 fn last_child_element(&self) -> Option<Self> {
951 let mut child_id = self.node().last_child?;
952 loop {
953 let child = self.dom.get(child_id)?;
954 if child.is_element() {
955 return Some(DomElement {
956 dom: self.dom,
957 id: child_id,
958 });
959 }
960 child_id = child.prev_sibling?;
961 }
962 }
963
964 fn is_root(&self) -> bool {
965 match self.node().parent {
966 Some(parent_id) => self
967 .dom
968 .get(parent_id)
969 .is_some_and(|n| matches!(n.data, NodeData::Document)),
970 None => false,
971 }
972 }
973
974 fn is_empty(&self) -> bool {
975 let mut child_id = self.node().first_child;
976 while let Some(id) = child_id {
977 if let Some(child) = self.dom.get(id) {
978 match &child.data {
979 NodeData::Element(_) => return false,
980 NodeData::Text(t) if !t.is_empty() => return false,
981 _ => {}
982 }
983 child_id = child.next_sibling;
984 } else {
985 break;
986 }
987 }
988 true
989 }
990
991 fn is_link(&self) -> bool {
992 let name = self.local_name();
993 (name == "a" || name == "area") && self.has_attribute("href")
994 }
995}
996
997#[cfg(test)]
1002mod tests {
1003 use super::*;
1004
1005 fn build_test_dom() -> Dom {
1006 let mut dom = Dom::new();
1007 let html = dom.create_element(QualName::new("html"), vec![]);
1008 dom.append_child(NodeId::DOCUMENT, html);
1009
1010 let body = dom.create_element(QualName::new("body"), vec![]);
1011 dom.append_child(html, body);
1012
1013 let div = dom.create_element(
1014 QualName::new("div"),
1015 vec![
1016 Attribute {
1017 name: QualName::new("id"),
1018 value: "main".to_string(),
1019 },
1020 Attribute {
1021 name: QualName::new("class"),
1022 value: "container active".to_string(),
1023 },
1024 ],
1025 );
1026 dom.append_child(body, div);
1027
1028 let p = dom.create_element(QualName::new("p"), vec![]);
1029 dom.append_child(div, p);
1030
1031 let text = dom.create_text("Hello world".to_string());
1032 dom.append_child(p, text);
1033
1034 dom
1035 }
1036
1037 #[test]
1038 fn new_dom_has_document() {
1039 let dom = Dom::new();
1040 assert_eq!(dom.len(), 1);
1041 assert!(dom.get(NodeId::DOCUMENT).is_some());
1042 }
1043
1044 #[test]
1045 fn create_and_append() {
1046 let mut dom = Dom::new();
1047 let div = dom.create_element(QualName::new("div"), vec![]);
1048 dom.append_child(NodeId::DOCUMENT, div);
1049
1050 assert_eq!(dom.children(NodeId::DOCUMENT), vec![div]);
1051 assert_eq!(dom.get(div).unwrap().parent, Some(NodeId::DOCUMENT));
1052 }
1053
1054 #[test]
1055 fn append_multiple_children() {
1056 let mut dom = Dom::new();
1057 let a = dom.create_element(QualName::new("a"), vec![]);
1058 let b = dom.create_element(QualName::new("b"), vec![]);
1059 let c = dom.create_element(QualName::new("c"), vec![]);
1060 dom.append_child(NodeId::DOCUMENT, a);
1061 dom.append_child(NodeId::DOCUMENT, b);
1062 dom.append_child(NodeId::DOCUMENT, c);
1063
1064 assert_eq!(dom.children(NodeId::DOCUMENT), vec![a, b, c]);
1065 assert_eq!(dom.get(a).unwrap().next_sibling, Some(b));
1066 assert_eq!(dom.get(b).unwrap().prev_sibling, Some(a));
1067 assert_eq!(dom.get(b).unwrap().next_sibling, Some(c));
1068 }
1069
1070 #[test]
1071 fn insert_before_test() {
1072 let mut dom = Dom::new();
1073 let a = dom.create_element(QualName::new("a"), vec![]);
1074 let c = dom.create_element(QualName::new("c"), vec![]);
1075 dom.append_child(NodeId::DOCUMENT, a);
1076 dom.append_child(NodeId::DOCUMENT, c);
1077
1078 let b = dom.create_element(QualName::new("b"), vec![]);
1079 dom.insert_before(NodeId::DOCUMENT, b, c);
1080
1081 assert_eq!(dom.children(NodeId::DOCUMENT), vec![a, b, c]);
1082 }
1083
1084 #[test]
1085 fn detach_test() {
1086 let mut dom = Dom::new();
1087 let a = dom.create_element(QualName::new("a"), vec![]);
1088 let b = dom.create_element(QualName::new("b"), vec![]);
1089 let c = dom.create_element(QualName::new("c"), vec![]);
1090 dom.append_child(NodeId::DOCUMENT, a);
1091 dom.append_child(NodeId::DOCUMENT, b);
1092 dom.append_child(NodeId::DOCUMENT, c);
1093
1094 dom.detach(b);
1095 assert_eq!(dom.children(NodeId::DOCUMENT), vec![a, c]);
1096 assert_eq!(dom.get(a).unwrap().next_sibling, Some(c));
1097 assert_eq!(dom.get(c).unwrap().prev_sibling, Some(a));
1098 }
1099
1100 #[test]
1101 fn remove_recycles() {
1102 let mut dom = Dom::new();
1103 let a = dom.create_element(QualName::new("a"), vec![]);
1104 dom.append_child(NodeId::DOCUMENT, a);
1105 dom.remove(a);
1106
1107 let b = dom.create_element(QualName::new("b"), vec![]);
1108 assert_eq!(b.0, a.0);
1109 }
1110
1111 #[test]
1112 fn text_content_test() {
1113 let mut dom = Dom::new();
1114 let div = dom.create_element(QualName::new("div"), vec![]);
1115 let text1 = dom.create_text("Hello ".to_string());
1116 let span = dom.create_element(QualName::new("span"), vec![]);
1117 let text2 = dom.create_text("world".to_string());
1118
1119 dom.append_child(NodeId::DOCUMENT, div);
1120 dom.append_child(div, text1);
1121 dom.append_child(div, span);
1122 dom.append_child(span, text2);
1123
1124 assert_eq!(dom.text_content(div), "Hello world");
1125 }
1126
1127 #[test]
1128 fn child_elements_skips_text() {
1129 let mut dom = Dom::new();
1130 let div = dom.create_element(QualName::new("div"), vec![]);
1131 let text = dom.create_text("hello".to_string());
1132 let span = dom.create_element(QualName::new("span"), vec![]);
1133
1134 dom.append_child(NodeId::DOCUMENT, div);
1135 dom.append_child(div, text);
1136 dom.append_child(div, span);
1137
1138 assert_eq!(dom.child_elements(div), vec![span]);
1139 }
1140
1141 #[test]
1142 fn reparent_children_test() {
1143 let mut dom = Dom::new();
1144 let source = dom.create_element(QualName::new("source"), vec![]);
1145 let target = dom.create_element(QualName::new("target"), vec![]);
1146 let a = dom.create_element(QualName::new("a"), vec![]);
1147 let b = dom.create_element(QualName::new("b"), vec![]);
1148
1149 dom.append_child(NodeId::DOCUMENT, source);
1150 dom.append_child(NodeId::DOCUMENT, target);
1151 dom.append_child(source, a);
1152 dom.append_child(source, b);
1153
1154 dom.reparent_children(source, target);
1155
1156 assert!(dom.children(source).is_empty());
1157 assert_eq!(dom.children(target), vec![a, b]);
1158 }
1159
1160 #[test]
1161 fn node_id_raw_roundtrip() {
1162 let id = NodeId::from_raw(42);
1163 assert_eq!(id.to_raw(), 42);
1164 }
1165
1166 #[test]
1167 fn set_text_content_test() {
1168 let mut dom = Dom::new();
1169 let div = dom.create_element(QualName::new("div"), vec![]);
1170 let span = dom.create_element(QualName::new("span"), vec![]);
1171 let text = dom.create_text("old".to_string());
1172 dom.append_child(NodeId::DOCUMENT, div);
1173 dom.append_child(div, span);
1174 dom.append_child(span, text);
1175
1176 dom.set_text_content(div, "new text");
1177 assert_eq!(dom.text_content(div), "new text");
1178 assert_eq!(dom.child_elements(div).len(), 0);
1179 }
1180
1181 #[test]
1182 fn get_element_by_id_test() {
1183 let mut dom = Dom::new();
1184 let div = dom.create_element(
1185 QualName::new("div"),
1186 vec![Attribute {
1187 name: QualName::new("id"),
1188 value: "main".to_string(),
1189 }],
1190 );
1191 dom.append_child(NodeId::DOCUMENT, div);
1192
1193 assert_eq!(dom.get_element_by_id("main"), Some(div));
1194 assert_eq!(dom.get_element_by_id("nonexistent"), None);
1195 }
1196
1197 #[test]
1198 fn get_elements_by_tag_name_test() {
1199 let mut dom = Dom::new();
1200 let div = dom.create_element(QualName::new("div"), vec![]);
1201 let p1 = dom.create_element(QualName::new("p"), vec![]);
1202 let p2 = dom.create_element(QualName::new("p"), vec![]);
1203 let span = dom.create_element(QualName::new("span"), vec![]);
1204 dom.append_child(NodeId::DOCUMENT, div);
1205 dom.append_child(div, p1);
1206 dom.append_child(div, span);
1207 dom.append_child(div, p2);
1208
1209 let ps = dom.get_elements_by_tag_name(NodeId::DOCUMENT, "p");
1210 assert_eq!(ps, vec![p1, p2]);
1211 }
1212
1213 #[test]
1214 fn get_elements_by_class_name_test() {
1215 let mut dom = Dom::new();
1216 let a = dom.create_element(
1217 QualName::new("div"),
1218 vec![Attribute {
1219 name: QualName::new("class"),
1220 value: "foo bar".to_string(),
1221 }],
1222 );
1223 let b = dom.create_element(
1224 QualName::new("div"),
1225 vec![Attribute {
1226 name: QualName::new("class"),
1227 value: "baz".to_string(),
1228 }],
1229 );
1230 dom.append_child(NodeId::DOCUMENT, a);
1231 dom.append_child(NodeId::DOCUMENT, b);
1232
1233 assert_eq!(
1234 dom.get_elements_by_class_name(NodeId::DOCUMENT, "foo"),
1235 vec![a]
1236 );
1237 assert_eq!(
1238 dom.get_elements_by_class_name(NodeId::DOCUMENT, "baz"),
1239 vec![b]
1240 );
1241 }
1242
1243 #[test]
1244 fn serialize_html_basic() {
1245 let mut dom = Dom::new();
1246 let div = dom.create_element(
1247 QualName::new("div"),
1248 vec![Attribute {
1249 name: QualName::new("id"),
1250 value: "main".to_string(),
1251 }],
1252 );
1253 let text = dom.create_text("Hello".to_string());
1254 dom.append_child(NodeId::DOCUMENT, div);
1255 dom.append_child(div, text);
1256
1257 assert_eq!(dom.serialize_html(div), "<div id=\"main\">Hello</div>");
1258 }
1259
1260 #[test]
1261 fn serialize_inner_html_test() {
1262 let mut dom = Dom::new();
1263 let div = dom.create_element(QualName::new("div"), vec![]);
1264 let span = dom.create_element(QualName::new("span"), vec![]);
1265 let text = dom.create_text("hi".to_string());
1266 dom.append_child(NodeId::DOCUMENT, div);
1267 dom.append_child(div, span);
1268 dom.append_child(span, text);
1269
1270 assert_eq!(dom.serialize_inner_html(div), "<span>hi</span>");
1271 }
1272
1273 #[test]
1274 fn merge_subtree_test() {
1275 let mut source = Dom::new();
1276 let div = source.create_element(QualName::new("div"), vec![]);
1277 let text = source.create_text("copied".to_string());
1278 source.append_child(NodeId::DOCUMENT, div);
1279 source.append_child(div, text);
1280
1281 let mut target = Dom::new();
1282 let parent = target.create_element(QualName::new("body"), vec![]);
1283 target.append_child(NodeId::DOCUMENT, parent);
1284
1285 let new_div = target.merge_subtree(&source, div);
1286 target.append_child(parent, new_div);
1287
1288 assert_eq!(target.text_content(new_div), "copied");
1289 assert_eq!(
1290 target
1291 .get(new_div)
1292 .and_then(|n| n.as_element())
1293 .map(|e| e.name.local.as_str()),
1294 Some("div")
1295 );
1296 }
1297
1298 #[test]
1299 fn append_child_rejects_self_cycle() {
1300 let mut dom = Dom::new();
1301 let div = dom.create_element(QualName::new("div"), vec![]);
1302 dom.append_child(NodeId::DOCUMENT, div);
1303 dom.append_child(div, div);
1304 assert_eq!(dom.children(NodeId::DOCUMENT), vec![div]);
1305 assert!(dom.children(div).is_empty());
1306 assert_eq!(dom.get(div).unwrap().parent, Some(NodeId::DOCUMENT));
1307 }
1308
1309 #[test]
1310 fn append_child_rejects_ancestor_cycle() {
1311 let mut dom = Dom::new();
1312 let outer = dom.create_element(QualName::new("outer"), vec![]);
1313 let inner = dom.create_element(QualName::new("inner"), vec![]);
1314 dom.append_child(NodeId::DOCUMENT, outer);
1315 dom.append_child(outer, inner);
1316 dom.append_child(inner, outer);
1317 assert_eq!(dom.children(NodeId::DOCUMENT), vec![outer]);
1318 assert_eq!(dom.children(outer), vec![inner]);
1319 assert!(dom.children(inner).is_empty());
1320 }
1321
1322 #[test]
1323 fn node_type_test() {
1324 let mut dom = Dom::new();
1325 assert_eq!(dom.node_type(NodeId::DOCUMENT), 9);
1326 let el = dom.create_element(QualName::new("div"), vec![]);
1327 assert_eq!(dom.node_type(el), 1);
1328 let text = dom.create_text("hi".to_string());
1329 assert_eq!(dom.node_type(text), 3);
1330 let comment = dom.create_comment("c".to_string());
1331 assert_eq!(dom.node_type(comment), 8);
1332 }
1333
1334 #[test]
1335 fn element_local_name() {
1336 let dom = build_test_dom();
1337 let html_el = DomElement::new(&dom, dom.children(NodeId::DOCUMENT)[0]).unwrap();
1338 assert_eq!(html_el.local_name(), "html");
1339 }
1340
1341 #[test]
1342 fn element_id_and_class() {
1343 let dom = build_test_dom();
1344 let html = dom.children(NodeId::DOCUMENT)[0];
1345 let body = dom.children(html)[0];
1346 let div = dom.children(body)[0];
1347 let el = DomElement::new(&dom, div).unwrap();
1348
1349 assert_eq!(el.id(), Some("main"));
1350 assert!(el.has_class("container"));
1351 assert!(el.has_class("active"));
1352 assert!(!el.has_class("inactive"));
1353 }
1354
1355 #[test]
1356 fn parent_and_children() {
1357 let dom = build_test_dom();
1358 let html = dom.children(NodeId::DOCUMENT)[0];
1359 let body = dom.children(html)[0];
1360 let div = dom.children(body)[0];
1361 let p = dom.children(div)[0];
1362
1363 let p_el = DomElement::new(&dom, p).unwrap();
1364 let parent = p_el.parent_element().unwrap();
1365 assert_eq!(parent.local_name(), "div");
1366
1367 let div_el = DomElement::new(&dom, div).unwrap();
1368 let first_child = div_el.first_child_element().unwrap();
1369 assert_eq!(first_child.local_name(), "p");
1370 }
1371
1372 #[test]
1373 fn is_root_test() {
1374 let dom = build_test_dom();
1375 let html = dom.children(NodeId::DOCUMENT)[0];
1376 let body = dom.children(html)[0];
1377
1378 let html_el = DomElement::new(&dom, html).unwrap();
1379 assert!(html_el.is_root());
1380
1381 let body_el = DomElement::new(&dom, body).unwrap();
1382 assert!(!body_el.is_root());
1383 }
1384
1385 #[test]
1386 fn is_empty_test() {
1387 let dom = build_test_dom();
1388 let html = dom.children(NodeId::DOCUMENT)[0];
1389 let body = dom.children(html)[0];
1390 let div = dom.children(body)[0];
1391
1392 let div_el = DomElement::new(&dom, div).unwrap();
1393 assert!(!div_el.is_empty());
1394 }
1395
1396 #[test]
1398 fn bdd_parse_simple_html() {
1399 let mut dom = Dom::new();
1400 let html = dom.create_element(QualName::new("html"), vec![]);
1401 let body = dom.create_element(QualName::new("body"), vec![]);
1402 let h1 = dom.create_element(QualName::new("h1"), vec![]);
1403 let text = dom.create_text("Hello".to_string());
1404
1405 dom.append_child(NodeId::DOCUMENT, html);
1406 dom.append_child(html, body);
1407 dom.append_child(body, h1);
1408 dom.append_child(h1, text);
1409
1410 assert!(dom.get(NodeId::DOCUMENT).is_some());
1411 let h1_children = dom.children(h1);
1412 assert_eq!(h1_children.len(), 1);
1413 assert_eq!(dom.text_content(h1), "Hello");
1414 }
1415
1416 #[test]
1418 fn bdd_query_elements_by_tag() {
1419 let mut dom = Dom::new();
1420 let div = dom.create_element(
1421 QualName::new("div"),
1422 vec![Attribute {
1423 name: QualName::new("class"),
1424 value: "content".to_string(),
1425 }],
1426 );
1427 let p1 = dom.create_element(QualName::new("p"), vec![]);
1428 let t1 = dom.create_text("First".to_string());
1429 let p2 = dom.create_element(QualName::new("p"), vec![]);
1430 let t2 = dom.create_text("Second".to_string());
1431
1432 dom.append_child(NodeId::DOCUMENT, div);
1433 dom.append_child(div, p1);
1434 dom.append_child(p1, t1);
1435 dom.append_child(div, p2);
1436 dom.append_child(p2, t2);
1437
1438 let ps = dom.get_elements_by_tag_name(NodeId::DOCUMENT, "p");
1439 assert_eq!(ps.len(), 2);
1440 assert_eq!(dom.text_content(ps[0]), "First");
1441 }
1442
1443 #[test]
1445 fn bdd_mutate_dom_tree() {
1446 let mut dom = Dom::new();
1447 let div = dom.create_element(QualName::new("div"), vec![]);
1448 let span = dom.create_element(QualName::new("span"), vec![]);
1449 let old_text = dom.create_text("Old".to_string());
1450
1451 dom.append_child(NodeId::DOCUMENT, div);
1452 dom.append_child(div, span);
1453 dom.append_child(span, old_text);
1454
1455 let p = dom.create_element(QualName::new("p"), vec![]);
1456 let new_text = dom.create_text("New".to_string());
1457 dom.append_child(div, p);
1458 dom.append_child(p, new_text);
1459
1460 assert_eq!(dom.children(div).len(), 2);
1461 }
1462}