1use std::collections::HashSet;
2
3use blitz_dom::{
4 Attribute as BlitzAttribute, BaseDocument, DocumentConfig, ElementData as BlitzElementData,
5 Node as BlitzNode, NodeData as BlitzNodeData, QualName as H5QualName, ns,
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub struct NodeId(pub(crate) usize);
10
11impl NodeId {
12 pub const DOCUMENT: NodeId = NodeId(0);
13
14 #[must_use]
15 pub fn from_raw(v: u32) -> Self {
16 Self(v as usize)
17 }
18
19 #[must_use]
20 pub fn to_raw(self) -> u32 {
21 self.0 as u32
22 }
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct QualName {
27 pub ns: Option<String>,
28 pub local: String,
29}
30
31impl QualName {
32 pub fn new(local: impl Into<String>) -> Self {
33 Self {
34 ns: None,
35 local: local.into(),
36 }
37 }
38
39 pub fn with_ns(ns: impl Into<String>, local: impl Into<String>) -> Self {
40 Self {
41 ns: Some(ns.into()),
42 local: local.into(),
43 }
44 }
45}
46
47impl QualName {
48 fn to_h5(&self) -> H5QualName {
49 let ns = match &self.ns {
50 Some(ns) => ns.as_str().into(),
51 None => ns!(html),
52 };
53 H5QualName::new(None, ns, self.local.as_str().into())
54 }
55
56 fn from_h5(qn: &H5QualName) -> Self {
57 let ns_str = qn.ns.to_string();
58 let ns = if ns_str.is_empty() || ns_str == "http://www.w3.org/1999/xhtml" {
59 None
60 } else {
61 Some(ns_str)
62 };
63 QualName {
64 ns,
65 local: qn.local.to_string(),
66 }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct Attribute {
72 pub name: QualName,
73 pub value: String,
74}
75
76impl Attribute {
77 pub(crate) fn to_blitz(&self) -> BlitzAttribute {
78 BlitzAttribute {
79 name: self.name.to_h5(),
80 value: self.value.clone(),
81 }
82 }
83}
84
85#[derive(Debug, Clone)]
86pub enum NodeData {
87 Document,
88 DocumentType {
89 name: String,
90 public_id: String,
91 system_id: String,
92 },
93 Element(ElementData),
94 Text(String),
95 Comment(String),
96 ProcessingInstruction {
97 target: String,
98 data: String,
99 },
100 DocumentFragment,
101 ShadowRoot {
102 mode: ShadowRootMode,
103 host: NodeId,
104 },
105}
106
107#[derive(Debug, Clone)]
108pub struct ElementData {
109 pub name: QualName,
110 pub attrs: Vec<Attribute>,
111 pub shadow_root: Option<NodeId>,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum ShadowRootMode {
116 Open,
117 Closed,
118}
119
120#[derive(Debug, Clone)]
121pub struct Node {
122 pub id: NodeId,
123 pub data: NodeData,
124 pub parent: Option<NodeId>,
125 pub first_child: Option<NodeId>,
126 pub last_child: Option<NodeId>,
127 pub prev_sibling: Option<NodeId>,
128 pub next_sibling: Option<NodeId>,
129}
130
131impl Node {
132 pub fn is_element(&self) -> bool {
133 matches!(self.data, NodeData::Element(_))
134 }
135
136 pub fn as_element(&self) -> Option<&ElementData> {
137 match &self.data {
138 NodeData::Element(data) => Some(data),
139 _ => None,
140 }
141 }
142
143 pub fn as_element_mut(&mut self) -> Option<&mut ElementData> {
144 match &mut self.data {
145 NodeData::Element(data) => Some(data),
146 _ => None,
147 }
148 }
149
150 pub fn as_text(&self) -> Option<&str> {
151 match &self.data {
152 NodeData::Text(t) => Some(t),
153 _ => None,
154 }
155 }
156
157 pub fn is_element_with_tag(&self, tag: &str) -> bool {
158 match &self.data {
159 NodeData::Element(e) => e.name.local.eq_ignore_ascii_case(tag),
160 _ => false,
161 }
162 }
163}
164
165pub struct Dom {
166 inner: BaseDocument,
167}
168
169const WALK_LIMIT: usize = 2_000_000;
170const ANCESTOR_LIMIT: usize = 10_000;
171
172impl Dom {
173 pub fn new() -> Self {
174 let mut config = DocumentConfig::default();
175 config.style_threading = blitz_dom::StyleThreading::Sequential;
176 let inner = BaseDocument::new(config);
177 Self { inner }
178 }
179
180 pub fn from_base(inner: BaseDocument) -> Self {
181 Self { inner }
182 }
183
184 pub fn document(&self) -> NodeId {
185 NodeId::DOCUMENT
186 }
187
188 pub fn inner(&self) -> &BaseDocument {
189 &self.inner
190 }
191
192 pub fn inner_mut(&mut self) -> &mut BaseDocument {
193 &mut self.inner
194 }
195
196 pub fn get(&self, id: NodeId) -> Option<Node> {
197 let blitz_node = self.inner.get_node(id.0)?;
198 Some(self.convert_node(blitz_node))
199 }
200
201 pub fn get_mut(&mut self, id: NodeId) -> Option<&mut BaseDocument> {
202 self.inner.get_node_mut(id.0)?;
203 Some(&mut self.inner)
204 }
205
206 pub fn len(&self) -> usize {
207 self.inner.tree().len()
208 }
209
210 pub fn is_empty(&self) -> bool {
211 self.inner.tree().is_empty()
212 }
213
214 pub fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) -> NodeId {
215 let blitz_attrs: Vec<BlitzAttribute> = attrs.iter().map(|a| a.to_blitz()).collect();
216 let h5_name = name.to_h5();
217 let elem_data = BlitzElementData::new(h5_name, blitz_attrs);
218 let id = self.inner.create_node(BlitzNodeData::Element(elem_data));
219 NodeId(id)
220 }
221
222 pub fn create_text(&mut self, text: String) -> NodeId {
223 let id = self.inner.create_text_node(&text);
224 NodeId(id)
225 }
226
227 pub fn create_comment(&mut self, _text: String) -> NodeId {
228 let id = self.inner.create_node(BlitzNodeData::Comment);
229 NodeId(id)
230 }
231
232 pub fn create_document_fragment(&mut self) -> NodeId {
233 let id = self.inner.create_node(BlitzNodeData::Document);
234 NodeId(id)
235 }
236
237 pub fn create_shadow_root(&mut self, _host: NodeId, _mode: ShadowRootMode) -> NodeId {
238 let id = self.inner.create_node(BlitzNodeData::Document);
239 NodeId(id)
240 }
241
242 pub fn allocate_pi(&mut self, _target: String, _data: String) -> NodeId {
243 let id = self.inner.create_node(BlitzNodeData::Comment);
244 NodeId(id)
245 }
246
247 pub fn create_doctype(
248 &mut self,
249 _name: String,
250 _public_id: String,
251 _system_id: String,
252 ) -> NodeId {
253 let id = self.inner.create_node(BlitzNodeData::Document);
254 NodeId(id)
255 }
256
257 pub fn append_child(&mut self, parent: NodeId, child: NodeId) {
258 if self.inner.get_node(parent.0).is_none() || self.inner.get_node(child.0).is_none() {
259 return;
260 }
261 self.detach(child);
262 if let Some(parent_node) = self.inner.get_node_mut(parent.0) {
263 parent_node.children.push(child.0);
264 }
265 if let Some(child_node) = self.inner.get_node_mut(child.0) {
266 child_node.parent = Some(parent.0);
267 }
268 }
269
270 pub fn insert_before(&mut self, parent: NodeId, child: NodeId, reference: NodeId) {
271 if self.inner.get_node(parent.0).is_none()
272 || self.inner.get_node(child.0).is_none()
273 || self.inner.get_node(reference.0).is_none()
274 {
275 return;
276 }
277 self.detach(child);
278 if let Some(parent_node) = self.inner.get_node_mut(parent.0) {
279 if let Some(idx) = parent_node
280 .children
281 .iter()
282 .position(|&id| id == reference.0)
283 {
284 parent_node.children.insert(idx, child.0);
285 } else {
286 parent_node.children.push(child.0);
287 }
288 }
289 if let Some(child_node) = self.inner.get_node_mut(child.0) {
290 child_node.parent = Some(parent.0);
291 }
292 }
293
294 pub fn detach(&mut self, id: NodeId) {
295 let parent_id = match self.inner.get_node(id.0) {
296 Some(n) => n.parent,
297 None => return,
298 };
299 if let Some(pid) = parent_id {
300 if let Some(parent) = self.inner.get_node_mut(pid) {
301 parent.children.retain(|&c| c != id.0);
302 }
303 }
304 if let Some(node) = self.inner.get_node_mut(id.0) {
305 node.parent = None;
306 }
307 }
308
309 pub fn remove(&mut self, id: NodeId) {
310 self.detach(id);
311 let children: Vec<usize> = self
312 .inner
313 .get_node(id.0)
314 .map(|n| n.children.clone())
315 .unwrap_or_default();
316 for child_id in children {
317 self.remove(NodeId(child_id));
318 }
319 }
320
321 pub fn reparent_children(&mut self, source: NodeId, target: NodeId) {
322 let children: Vec<usize> = self
323 .inner
324 .get_node(source.0)
325 .map(|n| n.children.clone())
326 .unwrap_or_default();
327 for child_id in children {
328 self.append_child(target, NodeId(child_id));
329 }
330 }
331
332 pub fn children(&self, parent: NodeId) -> Vec<NodeId> {
333 self.inner
334 .get_node(parent.0)
335 .map(|n| n.children.iter().map(|&id| NodeId(id)).collect())
336 .unwrap_or_default()
337 }
338
339 pub fn child_elements(&self, parent: NodeId) -> Vec<NodeId> {
340 self.children(parent)
341 .into_iter()
342 .filter(|id| self.get(*id).is_some_and(|n| n.is_element()))
343 .collect()
344 }
345
346 pub fn text_content(&self, id: NodeId) -> String {
347 let mut result = String::new();
348 self.collect_text(id, &mut result);
349 result
350 }
351
352 fn collect_text(&self, root: NodeId, result: &mut String) {
353 let mut stack: Vec<NodeId> = vec![root];
354 let mut visited: HashSet<NodeId> = HashSet::with_capacity(64);
355 let mut steps: usize = 0;
356 while let Some(id) = stack.pop() {
357 if !visited.insert(id) {
358 continue;
359 }
360 steps += 1;
361 if steps > WALK_LIMIT {
362 break;
363 }
364 let node = match self.get(id) {
365 Some(n) => n,
366 None => continue,
367 };
368 match &node.data {
369 NodeData::Text(t) => result.push_str(t),
370 _ => {
371 let mut kids: Vec<NodeId> = Vec::new();
372 let mut child = node.first_child;
373 while let Some(c) = child {
374 kids.push(c);
375 child = self.get(c).and_then(|n| n.next_sibling);
376 }
377 stack.extend(kids.into_iter().rev());
378 }
379 }
380 }
381 }
382
383 pub fn set_text_content(&mut self, id: NodeId, text: &str) {
384 let children: Vec<NodeId> = self.children(id);
385 for child in children {
386 self.remove(child);
387 }
388 if !text.is_empty() {
389 let text_id = self.create_text(text.to_string());
390 self.append_child(id, text_id);
391 }
392 }
393
394 pub fn get_element_by_id(&self, id_value: &str) -> Option<NodeId> {
395 self.find_element(NodeId::DOCUMENT, &|node| {
396 node.as_element()
397 .and_then(|e| e.attrs.iter().find(|a| a.name.local == "id"))
398 .is_some_and(|a| a.value == id_value)
399 })
400 }
401
402 pub fn get_elements_by_tag_name(&self, root: NodeId, tag: &str) -> Vec<NodeId> {
403 let mut results = Vec::new();
404 self.collect_elements(
405 root,
406 &|node| {
407 node.as_element()
408 .is_some_and(|e| e.name.local.eq_ignore_ascii_case(tag))
409 },
410 &mut results,
411 );
412 results
413 }
414
415 pub fn get_elements_by_class_name(&self, root: NodeId, class: &str) -> Vec<NodeId> {
416 let mut results = Vec::new();
417 self.collect_elements(
418 root,
419 &|node| {
420 node.as_element()
421 .and_then(|e| e.attrs.iter().find(|a| a.name.local == "class"))
422 .is_some_and(|a| a.value.split_whitespace().any(|c| c == class))
423 },
424 &mut results,
425 );
426 results
427 }
428
429 pub fn serialize_html(&self, id: NodeId) -> String {
430 let mut out = String::new();
431 self.serialize_node(id, &mut out);
432 out
433 }
434
435 pub fn serialize_inner_html(&self, id: NodeId) -> String {
436 let mut out = String::new();
437 let node = match self.get(id) {
438 Some(n) => n,
439 None => return out,
440 };
441 let mut kids: Vec<NodeId> = Vec::new();
442 let mut child = node.first_child;
443 while let Some(child_id) = child {
444 kids.push(child_id);
445 child = self.get(child_id).and_then(|n| n.next_sibling);
446 }
447 for c in kids {
448 self.serialize_node(c, &mut out);
449 }
450 out
451 }
452
453 fn serialize_node(&self, root: NodeId, out: &mut String) {
454 enum SerWork {
455 Open(NodeId),
456 Close(String),
457 }
458 const VOID_ELEMENTS: &[&str] = &[
459 "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param",
460 "source", "track", "wbr",
461 ];
462
463 let mut stack: Vec<SerWork> = vec![SerWork::Open(root)];
464 let mut visited: HashSet<NodeId> = HashSet::with_capacity(64);
465 let mut steps: usize = 0;
466 while let Some(work) = stack.pop() {
467 match work {
468 SerWork::Close(s) => out.push_str(&s),
469 SerWork::Open(id) => {
470 if !visited.insert(id) {
471 continue;
472 }
473 steps += 1;
474 if steps > WALK_LIMIT {
475 break;
476 }
477 let node = match self.get(id) {
478 Some(n) => n,
479 None => continue,
480 };
481 match &node.data {
482 NodeData::Element(elem) => {
483 out.push('<');
484 out.push_str(&elem.name.local);
485 for attr in &elem.attrs {
486 out.push(' ');
487 out.push_str(&attr.name.local);
488 out.push_str("=\"");
489 out.push_str(
490 &attr.value.replace('&', "&").replace('"', """),
491 );
492 out.push('"');
493 }
494 out.push('>');
495 let is_void = VOID_ELEMENTS.contains(&elem.name.local.as_str());
496 if !is_void {
497 stack.push(SerWork::Close(format!("</{}>", elem.name.local)));
498 }
499 let mut kids: Vec<NodeId> = Vec::new();
500 let mut child = node.first_child;
501 while let Some(c) = child {
502 kids.push(c);
503 child = self.get(c).and_then(|n| n.next_sibling);
504 }
505 for c in kids.into_iter().rev() {
506 stack.push(SerWork::Open(c));
507 }
508 }
509 NodeData::Text(text) => {
510 out.push_str(
511 &text
512 .replace('&', "&")
513 .replace('<', "<")
514 .replace('>', ">"),
515 );
516 }
517 NodeData::Comment(text) => {
518 out.push_str("<!--");
519 out.push_str(text);
520 out.push_str("-->");
521 }
522 NodeData::DocumentType { name, .. } => {
523 out.push_str("<!DOCTYPE ");
524 out.push_str(name);
525 out.push('>');
526 }
527 NodeData::Document | NodeData::DocumentFragment => {
528 let mut kids: Vec<NodeId> = Vec::new();
529 let mut child = node.first_child;
530 while let Some(c) = child {
531 kids.push(c);
532 child = self.get(c).and_then(|n| n.next_sibling);
533 }
534 for c in kids.into_iter().rev() {
535 stack.push(SerWork::Open(c));
536 }
537 }
538 _ => {}
539 }
540 }
541 }
542 }
543 }
544
545 pub fn merge_subtree(&mut self, source: &Dom, source_root: NodeId) -> NodeId {
546 fn create_from(this: &mut Dom, source: &Dom, src_id: NodeId) -> Option<NodeId> {
547 let src = source.get(src_id)?;
548 Some(match &src.data {
549 NodeData::Element(elem) => {
550 this.create_element(elem.name.clone(), elem.attrs.clone())
551 }
552 NodeData::Text(t) => this.create_text(t.clone()),
553 NodeData::Comment(t) => this.create_comment(t.clone()),
554 NodeData::DocumentFragment | NodeData::Document => this.create_document_fragment(),
555 _ => this.create_document_fragment(),
556 })
557 }
558
559 let new_root = match create_from(self, source, source_root) {
560 Some(id) => id,
561 None => return self.create_document_fragment(),
562 };
563
564 let mut queue: Vec<(NodeId, NodeId)> = Vec::new();
565 let mut visited: HashSet<NodeId> = HashSet::with_capacity(64);
566 visited.insert(source_root);
567
568 let mut child = source.get(source_root).and_then(|n| n.first_child);
569 while let Some(c) = child {
570 queue.push((c, new_root));
571 child = source.get(c).and_then(|n| n.next_sibling);
572 }
573
574 let mut steps: usize = 0;
575 let mut i = 0usize;
576 while i < queue.len() {
577 let (src_id, dest_parent) = queue[i];
578 i += 1;
579 steps += 1;
580 if steps > WALK_LIMIT {
581 break;
582 }
583 if !visited.insert(src_id) {
584 continue;
585 }
586 let new_id = match create_from(self, source, src_id) {
587 Some(id) => id,
588 None => continue,
589 };
590 self.append_child(dest_parent, new_id);
591 let mut child = source.get(src_id).and_then(|n| n.first_child);
592 while let Some(c) = child {
593 queue.push((c, new_id));
594 child = source.get(c).and_then(|n| n.next_sibling);
595 }
596 }
597
598 new_root
599 }
600
601 pub fn node_type(&self, id: NodeId) -> u32 {
602 match self.get(id).map(|n| n.data) {
603 Some(NodeData::Element(_)) => 1,
604 Some(NodeData::Text(_)) => 3,
605 Some(NodeData::ProcessingInstruction { .. }) => 7,
606 Some(NodeData::Comment(_)) => 8,
607 Some(NodeData::Document) => 9,
608 Some(NodeData::DocumentType { .. }) => 10,
609 Some(NodeData::DocumentFragment) => 11,
610 Some(NodeData::ShadowRoot { .. }) => 11,
611 None => 0,
612 }
613 }
614
615 fn find_element(&self, root: NodeId, predicate: &dyn Fn(&Node) -> bool) -> Option<NodeId> {
616 let mut stack: Vec<NodeId> = Vec::new();
617 let mut child = self.get(root).and_then(|n| n.first_child);
618 let mut seed: Vec<NodeId> = Vec::new();
619 while let Some(c) = child {
620 seed.push(c);
621 child = self.get(c).and_then(|n| n.next_sibling);
622 }
623 stack.extend(seed.into_iter().rev());
624
625 let mut visited: HashSet<NodeId> = HashSet::with_capacity(64);
626 let mut steps: usize = 0;
627 while let Some(id) = stack.pop() {
628 if !visited.insert(id) {
629 continue;
630 }
631 steps += 1;
632 if steps > WALK_LIMIT {
633 break;
634 }
635 let node = match self.get(id) {
636 Some(n) => n,
637 None => continue,
638 };
639 if predicate(&node) {
640 return Some(id);
641 }
642 let mut kids: Vec<NodeId> = Vec::new();
643 let mut child = node.first_child;
644 while let Some(c) = child {
645 kids.push(c);
646 child = self.get(c).and_then(|n| n.next_sibling);
647 }
648 stack.extend(kids.into_iter().rev());
649 }
650 None
651 }
652
653 fn collect_elements(
654 &self,
655 root: NodeId,
656 predicate: &dyn Fn(&Node) -> bool,
657 results: &mut Vec<NodeId>,
658 ) {
659 let mut stack: Vec<NodeId> = Vec::new();
660 let mut seed: Vec<NodeId> = Vec::new();
661 let mut child = self.get(root).and_then(|n| n.first_child);
662 while let Some(c) = child {
663 seed.push(c);
664 child = self.get(c).and_then(|n| n.next_sibling);
665 }
666 stack.extend(seed.into_iter().rev());
667
668 let mut visited: HashSet<NodeId> = HashSet::with_capacity(64);
669 let mut steps: usize = 0;
670 while let Some(id) = stack.pop() {
671 if !visited.insert(id) {
672 continue;
673 }
674 steps += 1;
675 if steps > WALK_LIMIT {
676 break;
677 }
678 let node = match self.get(id) {
679 Some(n) => n,
680 None => continue,
681 };
682 if predicate(&node) {
683 results.push(id);
684 }
685 let mut kids: Vec<NodeId> = Vec::new();
686 let mut child = node.first_child;
687 while let Some(c) = child {
688 kids.push(c);
689 child = self.get(c).and_then(|n| n.next_sibling);
690 }
691 stack.extend(kids.into_iter().rev());
692 }
693 }
694
695 fn convert_node(&self, blitz_node: &BlitzNode) -> Node {
696 let id = NodeId(blitz_node.id);
697 let parent = blitz_node.parent.map(NodeId);
698 let children: Vec<NodeId> = blitz_node.children.iter().map(|&c| NodeId(c)).collect();
699 let first_child = children.first().copied();
700 let last_child = children.last().copied();
701 let prev_sibling = blitz_node
702 .parent
703 .and_then(|pid| self.inner.get_node(pid))
704 .and_then(|parent| {
705 let pos = parent.children.iter().position(|&c| c == blitz_node.id)?;
706 if pos > 0 {
707 parent.children.get(pos - 1).map(|&c| NodeId(c))
708 } else {
709 None
710 }
711 });
712 let next_sibling = blitz_node
713 .parent
714 .and_then(|pid| self.inner.get_node(pid))
715 .and_then(|parent| {
716 let pos = parent.children.iter().position(|&c| c == blitz_node.id)?;
717 parent.children.get(pos + 1).map(|&c| NodeId(c))
718 });
719 let data = match &blitz_node.data {
720 BlitzNodeData::Document => NodeData::Document,
721 BlitzNodeData::Element(e) => {
722 let name = QualName::from_h5(&e.name);
723 let attrs = e
724 .attrs
725 .iter()
726 .map(|a| Attribute {
727 name: QualName::from_h5(&a.name),
728 value: a.value.clone(),
729 })
730 .collect();
731 NodeData::Element(ElementData {
732 name,
733 attrs,
734 shadow_root: None,
735 })
736 }
737 BlitzNodeData::AnonymousBlock(e) => {
738 let name = QualName::from_h5(&e.name);
739 let attrs = e
740 .attrs
741 .iter()
742 .map(|a| Attribute {
743 name: QualName::from_h5(&a.name),
744 value: a.value.clone(),
745 })
746 .collect();
747 NodeData::Element(ElementData {
748 name,
749 attrs,
750 shadow_root: None,
751 })
752 }
753 BlitzNodeData::Text(t) => NodeData::Text(t.content.clone()),
754 BlitzNodeData::Comment => NodeData::Comment(String::new()),
755 };
756 Node {
757 id,
758 data,
759 parent,
760 first_child,
761 last_child,
762 prev_sibling,
763 next_sibling,
764 }
765 }
766}
767
768impl Default for Dom {
769 fn default() -> Self {
770 Self::new()
771 }
772}
773
774impl std::fmt::Debug for Dom {
775 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
776 let mut s = f.debug_struct("Dom");
777 s.field("len", &self.inner.tree().len());
778 s.finish()
779 }
780}
781
782#[derive(Clone)]
783pub struct DomElement<'a> {
784 pub dom: &'a Dom,
785 pub id: NodeId,
786 data: ElementData,
787}
788
789impl<'a> DomElement<'a> {
790 pub fn new(dom: &'a Dom, id: NodeId) -> Option<Self> {
791 let node = dom.get(id)?;
792 if !node.is_element() {
793 return None;
794 }
795 let data = node.as_element()?.clone();
796 Some(Self { dom, id, data })
797 }
798
799 pub fn node_id(&self) -> NodeId {
800 self.id
801 }
802
803 pub fn local_name(&self) -> &str {
804 &self.data.name.local
805 }
806
807 pub fn id(&self) -> Option<&str> {
808 self.data
809 .attrs
810 .iter()
811 .find(|a| a.name.local == "id")
812 .map(|a| a.value.as_str())
813 }
814
815 pub fn has_class(&self, name: &str) -> bool {
816 self.data
817 .attrs
818 .iter()
819 .find(|a| a.name.local == "class")
820 .is_some_and(|a| a.value.split_whitespace().any(|c| c == name))
821 }
822
823 pub fn has_attribute(&self, name: &str) -> bool {
824 self.data
825 .attrs
826 .iter()
827 .any(|a| a.name.local.eq_ignore_ascii_case(name))
828 }
829
830 pub fn attr(&self, name: &str) -> Option<&str> {
831 self.data
832 .attrs
833 .iter()
834 .find(|a| a.name.local.eq_ignore_ascii_case(name))
835 .map(|a| a.value.as_str())
836 }
837
838 fn node(&self) -> Node {
839 self.dom
840 .get(self.id)
841 .unwrap_or_else(|| panic!("DomElement::node invariant: node {} must exist", self.id.0))
842 }
843
844 fn element_data(&self) -> &ElementData {
845 &self.data
846 }
847}
848
849impl<'a> std::fmt::Debug for DomElement<'a> {
850 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
851 let data = self.element_data();
852 write!(f, "<{}", data.name.local)?;
853 for attr in &data.attrs {
854 write!(f, " {}=\"{}\"", attr.name.local, attr.value)?;
855 }
856 write!(f, ">")
857 }
858}