1 /**
2 * Copyright © DiamondMVC 2019
3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module diamond.xhtml.xhtmldocument;
7
8 import diamond.xhtml.xhtmlexception;
9 import diamond.dom.domdocument;
10 import diamond.dom.domnode;
11 import diamond.dom.domparsersettings;
12 import diamond.xhtml.xhtmlnode;
13
14 /// An XHTML document.
15 final class XHtmlDocument : DomDocument
16 {
17 private:
18 /// The root nodes.
19 XHtmlNode[] _rootNodes;
20 /// The doctype node.
21 XHtmlNode _doctype;
22 /// The head node.
23 XHtmlNode _head;
24 /// The body node.
25 XHtmlNode _body;
26
27 public:
28 final:
29 /**
30 * Creates a new xhtml document.
31 * Params:
32 * parserSettings = The settings used for parsing the document.
33 */
34 this(DomParserSettings parserSettings) @safe
35 {
36 super(parserSettings);
37 }
38
39 /**
40 * Parses the elements from the dom to the document.
41 * Params:
42 * elements = The parsed dom elements.
43 */
44 override void parseElements(DomNode[] elements) @safe
45 {
46 if (!elements)
47 {
48 return;
49 }
50
51 foreach (element; elements)
52 {
53 import std..string : toLower;
54
55 if (element.name.toLower() == "doctype")
56 {
57 _doctype = element;
58 }
59 else
60 {
61 if (element.name.toLower() == "head")
62 {
63 _head = element;
64 }
65 else if (element.name.toLower() == "body")
66 {
67 _body = element;
68 }
69 else
70 {
71 if (element.name.toLower() == "html")
72 {
73 if (element.children)
74 {
75 foreach (child; element.children)
76 {
77 if (child.name.toLower() == "head")
78 {
79 _head = child;
80 }
81 else if (child.name.toLower() == "body")
82 {
83 _body = child;
84 }
85 }
86 }
87 }
88 }
89
90 _rootNodes ~= element;
91 }
92 }
93 }
94
95 @property
96 {
97 /// Gets the root nodes of the html document.
98 XHtmlNode[] rootNodes() @safe { return _rootNodes; }
99
100 /// Sets the root nodes of the html document.
101 void root(XHtmlNode[] nodes) @safe
102 {
103 _rootNodes = nodes;
104
105 if (!_rootNodes)
106 {
107 return;
108 }
109
110 foreach (element; _rootNodes)
111 {
112 import std..string : toLower;
113
114 if (element.name.toLower() == "doctype")
115 {
116 _doctype = element;
117 }
118 else if (element.name.toLower() == "head")
119 {
120 _head = element;
121 }
122 else if (element.name.toLower() == "body")
123 {
124 _body = element;
125 }
126 else if (element.name.toLower() == "html")
127 {
128 if (element.children)
129 {
130 foreach (child; element.children)
131 {
132 if (child.name.toLower() == "head")
133 {
134 _head = child;
135 }
136 else if (child.name.toLower() == "body")
137 {
138 _body = child;
139 }
140 }
141 }
142 }
143 }
144 }
145
146 /// Gets the head node.
147 XHtmlNode head() @safe { return _head; }
148
149 /// Gets the body node.
150 XHtmlNode body() @safe { return _body; }
151 }
152
153 /**
154 * Queries all dom nodes based on a css3 selector.
155 * Params:
156 * selector = The css3 selector.
157 * Returns:
158 * An array of all matching nodes.
159 */
160 XHtmlNode[] querySelectorAll(string selector)
161 {
162 import std.array : array;
163 import std.algorithm : map, filter, sort, group;
164
165 XHtmlNode[] elements;
166
167 auto dummyNode = new XHtmlNode(null);
168
169 foreach (rootNode; _rootNodes)
170 {
171 dummyNode.addChild(rootNode);
172
173 elements ~= dummyNode.querySelectorAll(selector);
174 }
175
176 return elements ? elements.sort.group.map!(g => g[0]).array : [];
177 }
178
179 /**
180 * Queries the first dom node based on a css3 selector.
181 * Params:
182 * selector = The css3 selector.
183 * Returns:
184 * The node if found, null otherwise.
185 */
186 XHtmlNode querySelector(string selector)
187 {
188 auto result = querySelectorAll(selector);
189
190 if (!result || !result.length)
191 {
192 return null;
193 }
194
195 return result[0];
196 }
197
198 /**
199 * Gets a dom node by an attribute named "id" matching the given value.
200 * Params:
201 * id = The id of the node to retrieve.
202 * Returns:
203 * The dom node if found, null otherwise.
204 */
205 XHtmlNode getElementById(string id) @safe
206 {
207 foreach (rootNode; _rootNodes)
208 {
209 if (rootNode.hasAttribute("id", id))
210 {
211 return rootNode;
212 }
213
214 auto element = rootNode.getElementById(id);
215
216 if (element)
217 {
218 return element;
219 }
220 }
221
222 return null;
223 }
224
225 /// XHtml documents cannot be repaired. Use HtmlDocument.repairDocument() instead.
226 override void repairDocument() @safe
227 {
228 throw new XHtmlException("Cannot repair XHtml documents, because they're strict html. Use HtmlDocument.repairDocument() instead.");
229 }
230
231 /**
232 * Converts the xhtml document to a properly formatted xhtml document-string.
233 * Returns:
234 * A string equivalent to the properly formatted xhtml document-string.
235 */
236 override string toString() @safe
237 {
238 import std.array : join, array;
239 import std.algorithm : map;
240 import std..string : format;
241
242 return (_doctype ? "<!%s %s>\r\n".format(_doctype.name, _doctype.getAttributes().map!(a => a.value ? "%s=\"%s\"".format(a.name, a.value) : a.name).array.join(" ")) : "") ~ (_rootNodes ? join(_rootNodes.map!(n => n.toString).array, "\r\n") : "");
243 }
244 }