Skip to main content

parse_html/
parse_html.rs

1//! Parse an HTML-ish template and walk the resulting tree.
2//!
3//! Run with: `cargo run --example parse_html -p rdom-parser`
4
5use rdom_core::{Dom, NodeId, NodeType};
6use rdom_parser::parse;
7
8fn main() {
9    let template = r#"
10        <div class="card" id="hero">
11          <h1>Welcome &amp; hello</h1>
12          <p>This is <strong>rdom-parser</strong>.</p>
13          <!-- TODO: add icon -->
14          <ul>
15            <li>Zero external deps</li>
16            <li>Full UTF-8: δΈ­ζ–‡, πŸ¦€, πŸ‘¨β€πŸ‘©β€πŸ‘§</li>
17            <li>Error reporting with line/col + hints</li>
18          </ul>
19          <button disabled>OK</button>
20        </div>
21    "#;
22
23    let (dom, _ids): (Dom<()>, _) = parse(template).unwrap();
24    let root = dom.root();
25
26    println!("── Parsed tree ─────────────────────────────────");
27    walk(&dom, root, 0);
28
29    println!("\n── Selector queries ────────────────────────────");
30    let lis = dom.query_selector_all_in(root, "li").unwrap();
31    println!("Found {} <li>:", lis.len());
32    for li in lis {
33        println!("  β€’ {}", dom.text_content(li));
34    }
35
36    let card = dom.query_selector_in(root, ".card").unwrap().unwrap();
37    println!(
38        "\n.card's id attribute: {:?}",
39        dom.get_attribute(card, "id")
40    );
41
42    let button = dom.query_selector_in(root, "button[disabled]").unwrap();
43    if let Some(b) = button {
44        println!("Disabled button: {}", dom.text_content(b));
45    }
46
47    println!("\n── Text content (flattened) ────────────────────");
48    println!(
49        "{}",
50        dom.text_content(card)
51            .split_whitespace()
52            .collect::<Vec<_>>()
53            .join(" ")
54    );
55}
56
57/// Pretty-print a subtree.
58fn walk(dom: &Dom<()>, id: NodeId, depth: usize) {
59    let indent = "  ".repeat(depth);
60    let node = dom.node(id);
61    match node.node_type() {
62        NodeType::Element => {
63            let tag = node.tag_name().unwrap_or("?");
64            let attrs: Vec<String> = dom
65                .attributes(id)
66                .map(|(k, v)| format!(r#" {k}="{v}""#))
67                .collect();
68            let classes: Vec<&str> = dom.class_list(id).collect();
69            let class_attr = if classes.is_empty() {
70                String::new()
71            } else {
72                format!(r#" class="{}""#, classes.join(" "))
73            };
74            println!("{indent}<{tag}{class_attr}{}>", attrs.join(""));
75            for child in node.child_nodes() {
76                walk(dom, child.id(), depth + 1);
77            }
78            // Only print close tag if there are children or a bare
79            // self-close doesn't render well.
80            if node.has_child_nodes() {
81                println!("{indent}</{tag}>");
82            }
83        }
84        NodeType::Text => {
85            let s = node.node_value().unwrap_or("").trim();
86            if !s.is_empty() {
87                println!("{indent}{s:?}");
88            }
89        }
90        NodeType::Comment => {
91            println!("{indent}<!--{}-->", node.node_value().unwrap_or(""));
92        }
93        NodeType::Fragment => {
94            for child in node.child_nodes() {
95                walk(dom, child.id(), depth);
96            }
97        }
98        // `NodeType` is `#[non_exhaustive]`: later node kinds print
99        // their type.
100        other => println!("{indent}<!-- {other:?} -->"),
101    }
102}