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
111
112
113
114
115
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::str::from_utf8;

use crate::spec::tag::ns::Namespace;

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ElementClosingTag {
    Omitted,
    Present,
    SelfClosing,
    Void,
}

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ScriptOrStyleLang {
    CSS,
    Data,
    JS,
}

// Derive Eq for testing.
#[derive(Eq, PartialEq)]
pub enum NodeData {
    Bang {
        code: Vec<u8>,
        // If the source unexpectedly ended before `>`, we can't add it, as otherwise output could be longer than source.
        ended: bool,
    },
    Comment {
        code: Vec<u8>,
        // If the source unexpectedly ended before `-->`, we can't add it, as otherwise output could be longer than source.
        ended: bool,
    },
    Element {
        attributes: HashMap<Vec<u8>, Vec<u8>>,
        children: Vec<NodeData>,
        // If the source doesn't have a closing tag, then we can't add one, as otherwise output could be longer than source.
        closing_tag: ElementClosingTag,
        name: Vec<u8>,
        namespace: Namespace,
        // WARNING: This should only be set during minification, as minification can alter tree (e.g. remove text nodes completely).
        // If the next text or element sibling is an element, this will be set to its tag name.
        // Otherwise, this will be empty. It should be empty on creation.
        next_sibling_element_name: Vec<u8>,
    },
    Instruction {
        code: Vec<u8>,
        // If the source unexpectedly ended before `?>`, we can't add it, as otherwise output could be longer than source.
        ended: bool,
    },
    // Entities should not be decoded in ScriptOrStyleContent.
    ScriptOrStyleContent {
        code: Vec<u8>,
        lang: ScriptOrStyleLang,
    },
    Text {
        value: Vec<u8>,
    },
}

fn str(bytes: &[u8]) -> &str {
    from_utf8(bytes).unwrap()
}

impl Debug for NodeData {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            NodeData::Bang { code, ended } => f
                .debug_struct("Bang")
                .field("code", &from_utf8(code).unwrap().to_string())
                .field("ended", ended)
                .finish(),
            NodeData::Comment { code, ended } => f
                .debug_struct("Comment")
                .field("code", &from_utf8(code).unwrap().to_string())
                .field("ended", ended)
                .finish(),
            NodeData::Element {
                attributes,
                children,
                closing_tag,
                name,
                namespace,
                next_sibling_element_name,
            } => f
                .debug_struct("Element")
                .field("tag", &{
                    let mut out = format!("{:?}:{}", namespace, str(name));
                    for (n, v) in attributes {
                        out.push_str(format!(" {}={}", str(n), str(v)).as_str());
                    }
                    out
                })
                .field("children", children)
                .field("closing_tag", closing_tag)
                .field(
                    "next_sibling_element_name",
                    &from_utf8(next_sibling_element_name).unwrap().to_string(),
                )
                .finish(),
            NodeData::Instruction { code, ended } => f
                .debug_struct("Instruction")
                .field("code", &from_utf8(code).unwrap().to_string())
                .field("ended", ended)
                .finish(),
            NodeData::ScriptOrStyleContent { code, lang } => f
                .debug_struct("ScriptOrStyleContent")
                .field("code", &from_utf8(code).unwrap().to_string())
                .field("lang", lang)
                .finish(),
            NodeData::Text { value } => f.write_str(str(value)),
        }
    }
}