1import { ElementType } from "domelementtype";
2
3const nodeTypes = new Map<ElementType, number>([
4    [ElementType.Tag, 1],
5    [ElementType.Script, 1],
6    [ElementType.Style, 1],
7    [ElementType.Directive, 1],
8    [ElementType.Text, 3],
9    [ElementType.CDATA, 4],
10    [ElementType.Comment, 8]
11]);
12
13// This object will be used as the prototype for Nodes when creating a
14// DOM-Level-1-compliant structure.
15export class Node {
16    /** Parent of the node */
17    parent: NodeWithChildren | null = null;
18
19    /** Previous sibling */
20    prev: Node | null = null;
21
22    /** Next sibling */
23    next: Node | null = null;
24
25    /** The start index of the node. Requires `withStartIndices` on the handler to be `true. */
26    startIndex: number | null = null;
27
28    /** The end index of the node. Requires `withEndIndices` on the handler to be `true. */
29    endIndex: number | null = null;
30
31    /**
32     *
33     * @param type The type of the node.
34     */
35    constructor(public type: ElementType) {}
36
37    // Read-only aliases
38    get nodeType(): number {
39        return nodeTypes.get(this.type) || 1;
40    }
41
42    // Read-write aliases for properties
43    get parentNode(): NodeWithChildren | null {
44        return this.parent || null;
45    }
46
47    set parentNode(parent: NodeWithChildren | null) {
48        this.parent = parent;
49    }
50
51    get previousSibling(): Node | null {
52        return this.prev || null;
53    }
54
55    set previousSibling(prev: Node | null) {
56        this.prev = prev;
57    }
58
59    get nextSibling(): Node | null {
60        return this.next || null;
61    }
62
63    set nextSibling(next: Node | null) {
64        this.next = next;
65    }
66}
67
68export class DataNode extends Node {
69    /**
70     *
71     * @param type The type of the node
72     * @param data The content of the data node
73     */
74    constructor(
75        type: ElementType.Comment | ElementType.Text | ElementType.Directive,
76        public data: string
77    ) {
78        super(type);
79    }
80
81    get nodeValue(): string {
82        return this.data;
83    }
84
85    set nodeValue(data: string) {
86        this.data = data;
87    }
88}
89
90export class ProcessingInstruction extends DataNode {
91    constructor(public name: string, data: string) {
92        super(ElementType.Directive, data);
93    }
94}
95
96export class NodeWithChildren extends Node {
97    /**
98     *
99     * @param type Type of the node.
100     * @param children Children of the node. Only certain node types can have children.
101     */
102    constructor(
103        type:
104            | ElementType.CDATA
105            | ElementType.Script
106            | ElementType.Style
107            | ElementType.Tag,
108        public children: Node[]
109    ) {
110        super(type);
111    }
112
113    // Aliases
114    get firstChild(): Node | null {
115        return this.children[0] || null;
116    }
117
118    get lastChild(): Node | null {
119        return this.children[this.children.length - 1] || null;
120    }
121
122    get childNodes(): Node[] {
123        return this.children;
124    }
125
126    set childNodes(children: Node[]) {
127        this.children = children;
128    }
129}
130
131export class Element extends NodeWithChildren {
132    /**
133     *
134     * @param name Name of the tag, eg. `div`, `span`
135     * @param attribs Object mapping attribute names to attribute values
136     */
137    constructor(
138        public name: string,
139        public attribs: { [name: string]: string }
140    ) {
141        super(
142            name === "script"
143                ? ElementType.Script
144                : name === "style"
145                ? ElementType.Style
146                : ElementType.Tag,
147            []
148        );
149        this.attribs = attribs;
150    }
151
152    // DOM Level 1 aliases
153    get tagName(): string {
154        return this.name;
155    }
156
157    set tagName(name: string) {
158        this.name = name;
159    }
160}
161