1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
use crate::{Element, Node};

pub trait ToHtml {
    fn to_html(&self, buf: &mut String, within_inline: bool);
}

fn push_esc(s: &str, buf: &mut String) {
    for c in s.chars() {
        match c {
            '<' => buf.push_str("&lt;"),
            '>' => buf.push_str("&gt;"),
            '&' => buf.push_str("&amp;"),
            '"' => buf.push_str("&quot;"),
            '\u{A0}' => buf.push_str("&nbsp;"),
            '\0' => buf.push('\u{FFFD}'),
            _ => buf.push(c),
        }
    }
}

fn push_noesc(s: &str, buf: &mut String) {
    for c in s.chars() {
        if c == '\0' {
            buf.push('\u{FFFD}');
        } else {
            buf.push(c);
        }
    }
}

impl ToHtml for Node<'_> {
    fn to_html(&self, buf: &mut String, within_inline: bool) {
        match self {
            Node::Element(e) => e.to_html(buf, within_inline),
            &Node::Text(t) => push_esc(t, buf),
            Node::Text2(t) => push_esc(t, buf),
            &Node::Entity(t) => {
                buf.push('&');
                buf.push_str(t);
            }
            Node::Verbatim(t) => push_noesc(t, buf),
            &Node::Cdata(c) => {
                buf.push_str("<![CDATA[");
                push_noesc(c, buf);
                buf.push_str("]]>");
            }
            Node::Comment(content) => {
                buf.push_str("<!--");
                push_noesc(content, buf);
                buf.push_str("-->");
                if !within_inline {
                    buf.push('\n');
                }
            }
            &Node::Doctype(d) => push_noesc(d, buf),
            Node::Fragment(f) => {
                for n in f {
                    n.to_html(buf, within_inline);
                }
            }
        }
    }
}

impl ToHtml for Element<'_> {
    fn to_html(&self, buf: &mut String, within_inline: bool) {
        if within_inline && self.is_block_level {
            buf.push('\n');
        }
        buf.push('<');
        buf.push_str(self.name.as_str());

        for attr in &self.attrs {
            buf.push(' ');
            push_noesc(attr.key, buf);

            if let Some(value) = &attr.value {
                buf.push_str("=\"");
                push_esc(value, buf);
                buf.push('"');
            }
        }

        if let Some(content) = &self.content {
            buf.push('>');

            if self.contains_blocks {
                buf.push('\n');
            }
            content.to_html(buf, !self.contains_blocks);

            buf.push_str("</");
            buf.push_str(self.name.as_str());
            buf.push('>');
        } else {
            buf.push_str("/>");
        }
        if self.is_block_level {
            buf.push('\n');
        }
    }
}

impl ToHtml for [Node<'_>] {
    fn to_html(&self, buf: &mut String, within_inline: bool) {
        for n in self {
            n.to_html(buf, within_inline);
        }
    }
}