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 }