cmark_writer/ast/
html.rs

1//! HTML element definitions and utilities for CommonMark AST.
2//!
3//! This module contains definitions for HTML elements and attributes in the AST,
4//! along with utilities for safely handling HTML content.
5
6use super::Node;
7
8/// HTML attribute
9#[derive(Debug, Clone, PartialEq)]
10pub struct HtmlAttribute {
11    /// Attribute name
12    pub name: String,
13    /// Attribute value
14    pub value: String,
15}
16
17/// HTML element
18#[derive(Debug, Clone, PartialEq)]
19pub struct HtmlElement {
20    /// HTML tag name
21    pub tag: String,
22    /// HTML attributes
23    pub attributes: Vec<HtmlAttribute>,
24    /// Child nodes
25    pub children: Vec<Node>,
26    /// Whether this is a self-closing element
27    pub self_closing: bool,
28}
29
30impl HtmlElement {
31    /// Create a new HTML element
32    pub fn new(tag: &str) -> Self {
33        Self {
34            tag: tag.to_string(),
35            attributes: Vec::new(),
36            children: Vec::new(),
37            self_closing: false,
38        }
39    }
40
41    /// Add an attribute to the HTML element
42    pub fn with_attribute(mut self, name: &str, value: &str) -> Self {
43        self.attributes.push(HtmlAttribute {
44            name: name.to_string(),
45            value: value.to_string(),
46        });
47        self
48    }
49
50    /// Add multiple attributes to the HTML element
51    pub fn with_attributes(mut self, attrs: Vec<(&str, &str)>) -> Self {
52        for (name, value) in attrs {
53            self.attributes.push(HtmlAttribute {
54                name: name.to_string(),
55                value: value.to_string(),
56            });
57        }
58        self
59    }
60
61    /// Add child nodes to the HTML element
62    pub fn with_children(mut self, children: Vec<Node>) -> Self {
63        self.children = children;
64        self
65    }
66
67    /// Set whether the element is self-closing
68    pub fn self_closing(mut self, is_self_closing: bool) -> Self {
69        self.self_closing = is_self_closing;
70        self
71    }
72
73    /// Check if this element's tag matches any in the provided list (case-insensitive)
74    pub fn tag_matches_any(&self, tags: &[String]) -> bool {
75        tags.iter().any(|tag| tag.eq_ignore_ascii_case(&self.tag))
76    }
77}
78
79/// Safely escape HTML content
80///
81/// This function escapes the special HTML characters in a string
82/// to ensure it is safe for inclusion in HTML content.
83///
84/// # Arguments
85/// * `content` - The raw content to escape
86///
87/// # Returns
88/// The escaped HTML content
89pub fn escape_html(content: &str) -> String {
90    content
91        .replace('&', "&amp;")
92        .replace('<', "&lt;")
93        .replace('>', "&gt;")
94        .replace('"', "&quot;")
95        .replace('\'', "&#39;")
96}
97
98/// Creates a safe HTML node by filtering potentially unsafe elements
99///
100/// Processes an HTML element according to the provided filter rules.
101/// If the element is considered unsafe (matches a disallowed tag),
102/// it will be converted to escaped text.
103///
104/// # Arguments
105/// * `element` - The original HTML element
106/// * `disallowed_tags` - List of disallowed HTML tag names
107///
108/// # Returns
109/// Either the original element as a Node::HtmlElement or
110/// an escaped text representation as Node::Text
111pub fn safe_html(element: HtmlElement, disallowed_tags: &[String]) -> Node {
112    if element.tag_matches_any(disallowed_tags) {
113        // Convert to escaped text
114        let mut html_text = String::new();
115        html_text.push_str(&format!("&lt;{}", element.tag));
116
117        for attr in &element.attributes {
118            html_text.push_str(&format!(" {}=\"{}\"", attr.name, attr.value));
119        }
120
121        if element.self_closing {
122            html_text.push_str(" /&gt;");
123        } else {
124            html_text.push_str("&gt;");
125
126            // Add children content (simplified approach)
127            for child in &element.children {
128                html_text.push_str(&format!("{}", child));
129            }
130
131            html_text.push_str(&format!("&lt;/{}&gt;", element.tag));
132        }
133
134        Node::Text(html_text)
135    } else {
136        Node::HtmlElement(element)
137    }
138}