├── .travis.yml ├── .gitignore ├── src └── html │ ├── package.d │ ├── utils.d │ ├── parser.d │ ├── entities.d │ └── dom.d ├── .editorconfig ├── dub.json ├── LICENSE ├── scripts └── genentities.d └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | *.a 7 | *.lib 8 | -------------------------------------------------------------------------------- /src/html/package.d: -------------------------------------------------------------------------------- 1 | module html; 2 | 3 | 4 | public import html.dom; 5 | public import html.entities; 6 | public import html.parser; 7 | 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{c,h,d,di,dd,json}] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = tab 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "htmld", 3 | "description": "Lightweight and forgiving HTML parser", 4 | "copyright": "Copyright © 2015, Marcio Martins", 5 | "authors": [ "Márcio Martins" ], 6 | "license": "MIT License", 7 | "targetType": "library", 8 | "targetName": "htmld", 9 | "dependencies": { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 eBookingServices 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /scripts/genentities.d: -------------------------------------------------------------------------------- 1 | import std.algorithm; 2 | import std.array; 3 | import std.bitmanip; 4 | import std.conv; 5 | import std.file; 6 | import std.json; 7 | import std.stdio; 8 | import std.utf; 9 | 10 | 11 | void main() { 12 | auto text = readText("entities.json"); 13 | auto json = parseJSON(text); 14 | assert(json.type == JSON_TYPE.OBJECT); 15 | 16 | ubyte[] codes; 17 | uint[string] offsets; 18 | 19 | auto dchars = uninitializedArray!(dchar[])(32); 20 | auto minlen = int.max; 21 | auto maxlen = int.min; 22 | foreach(string key, value; json) { 23 | assert(value.type == JSON_TYPE.OBJECT); 24 | auto codepoints = value["codepoints"]; 25 | 26 | dchars.length = codepoints.array.length; 27 | foreach(uint i, dcharjson; codepoints) 28 | dchars[i] = cast(dchar)dcharjson.integer; 29 | 30 | uint offset = codes.length; 31 | 32 | auto code = cast(ubyte[])toUTF8(dchars); 33 | auto found = codes.find(code); 34 | if (found.length) { 35 | offset = codes.length - found.length; 36 | } else { 37 | codes ~= cast(ubyte[])toUTF8(dchars); 38 | } 39 | uint length = code.length; 40 | 41 | assert(offset < (1 << 28)); 42 | assert(length < (1 << 4)); 43 | offset = (offset << 4) | length; 44 | 45 | if (key.front == '&') 46 | key = key[1..$]; 47 | if (key.back == ';') 48 | key = key[0..$-1]; 49 | 50 | offsets[key] = offset; 51 | minlen = min(key.length, minlen); 52 | maxlen = max(key.length, maxlen); 53 | } 54 | 55 | auto output = appender!(const(char)[]); 56 | 57 | output.put("__gshared immutable static ubyte["); 58 | output.put(codes.length.to!string); 59 | output.put("] bytes_ = "); 60 | output.put(codes.to!string.replace(", ", ",")); 61 | output.put(";\n"); 62 | output.put("__gshared immutable static uint[const(char)[]] index_ = "); 63 | output.put(offsets.to!string.replace(", ", ",")); 64 | output.put(";\n"); 65 | output.put("enum MinEntityNameLength = "); 66 | output.put(minlen.to!string); 67 | output.put(";\n"); 68 | output.put("enum MaxEntityNameLength = "); 69 | output.put(maxlen.to!string); 70 | output.put(";\n"); 71 | 72 | std.file.write("entities.d.mixin", output.data); 73 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # htmld [![Build Status](https://travis-ci.org/eBookingServices/htmld.svg?branch=master)](https://travis-ci.org/eBookingServices/htmld) 2 | Lightweight and forgiving HTML parser and DOM. 3 | 4 | The parser was inspired by [htmlparse2](https://github.com/fb55/htmlparser2) by [fb55](https://github.com/fb55) 5 | 6 | HTML Entity parsing and decoding are both optional. The current parser interface is based on callbacks. 7 | It can also output compact HTML, and can do basic validation. 8 | 9 | 10 | Creating the DOM from source: 11 | ```d 12 | auto doc = createDocument(` `); 13 | writeln(doc.root.outerHTML); 14 | ``` 15 | 16 | 17 | Creating/mutating DOM manually: 18 | ```d 19 | auto doc = createDocument(); 20 | doc.root.html = ` `; 21 | 22 | auto container = doc.createElement("div", doc.root.firstChild); 23 | container.attr("class", "container"); 24 | container.html = "

moo!

"; 25 | 26 | auto app = appender!string; 27 | doc.root.outerHTML(app); 28 | ``` 29 | 30 | 31 | Compacting HTML: 32 | ```d 33 | auto doc = createDocument!(DOMCreateOptions.none)("\n abc \n"); 34 | writeln(doc.root.compactHTML); 35 | ``` 36 | 37 | 38 | QuerySelector interface: 39 | ```d 40 | if (auto p = doc.querySelector("p:nth-of-type(2)")) 41 | p.text = "mooo"; 42 | 43 | foreach(p; doc.querySelectorAll("p")) { 44 | p.text = "mooo"; 45 | } 46 | ``` 47 | 48 | 49 | Example parser usage: 50 | ```d 51 | auto builder = DOMBuilder(); 52 | parseHTML(` `, builder); 53 | ``` 54 | 55 | 56 | Example handler: 57 | ```d 58 | struct DOMBuilder { 59 | void onText(const(char)[] data) {} 60 | void onSelfClosing() {} 61 | void onOpenStart(const(char)[] data) {} 62 | void onOpenEnd(const(char)[] data) {} 63 | void onClose(const(char)[] data) {} 64 | void onAttrName(const(char)[] data) {} 65 | void onAttrEnd() {} 66 | void onAttrValue(const(char)[] data) {} 67 | void onComment(const(char)[] data) {} 68 | void onDeclaration(const(char)[] data) {} 69 | void onProcessingInstruction(const(char)[] data) {} 70 | void onCDATA(const(char)[] data) {} 71 | 72 | // the following are required if ParseEntities is set 73 | void onNamedEntity(const(char)[] data) {} 74 | void onNumericEntity(const(char)[] data) {} 75 | void onHexEntity(const(char)[] data) {} 76 | 77 | // required only if DecodeEntities is set 78 | void onEntity(const(char)[] data, const(char)[] decoded) {} 79 | 80 | void onDocumentEnd() {} 81 | } 82 | ``` 83 | 84 | 85 | # todo 86 | - implement range-based interface for parser 87 | - implement a basic validation mode 88 | -------------------------------------------------------------------------------- /src/html/utils.d: -------------------------------------------------------------------------------- 1 | module html.utils; 2 | 3 | 4 | import std.ascii; 5 | import std.typecons; 6 | 7 | 8 | bool isAllWhite(Char)(Char[] value) { 9 | auto ptr = value.ptr; 10 | const end = ptr + value.length; 11 | 12 | while (ptr != end) { 13 | if (!isWhite(*ptr++)) 14 | return false; 15 | } 16 | return true; 17 | } 18 | 19 | 20 | bool requiresQuotes(Char)(Char[] value) { 21 | auto ptr = value.ptr; 22 | const end = ptr + value.length; 23 | 24 | while (ptr != end) { 25 | switch (*ptr++) { 26 | case 'a': .. case 'z': 27 | case 'A': .. case 'Z': 28 | case '0': .. case '9': 29 | case '-': 30 | case '_': 31 | case '.': 32 | case ':': 33 | continue; 34 | default: 35 | return true; 36 | } 37 | } 38 | return false; 39 | } 40 | 41 | 42 | bool equalsCI(CharA, CharB)(const(CharA)[] a, const(CharB)[] b) { 43 | if (a.length == b.length) { 44 | for (size_t i; i < a.length; ++i) { 45 | if (std.ascii.toLower(a.ptr[i]) != std.ascii.toLower(b.ptr[i])) 46 | return false; 47 | } 48 | return true; 49 | } 50 | return false; 51 | } 52 | 53 | 54 | enum QuickHash64Seed = 14695981039346656037u; 55 | enum QuickHash64Scale = 1099511628211u; 56 | 57 | 58 | ulong quickHash64(const(char)* p, const(char)* pend, ulong hash = QuickHash64Seed) { 59 | while (p != pend) 60 | hash = (hash ^ cast(ulong)(*p++)) * QuickHash64Scale; 61 | return hash; 62 | } 63 | 64 | 65 | ulong quickHash64i(const(char)* p, const(char)* pend, ulong hash = QuickHash64Seed) { 66 | while (p != pend) 67 | hash = (hash ^ cast(ulong)(std.ascii.toLower(*p++))) * QuickHash64Scale; 68 | return hash; 69 | } 70 | 71 | 72 | hash_t quickHashOf(const(char)[] x) { 73 | return cast(hash_t)quickHash64(x.ptr, x.ptr + x.length); 74 | } 75 | 76 | 77 | hash_t tagHashOf(const(char)[] x) { 78 | return cast(hash_t)quickHash64i(x.ptr, x.ptr + x.length); 79 | } 80 | 81 | 82 | void writeQuotesEscaped(Appender)(ref Appender app, const(char)[] x) { 83 | auto ptr = x.ptr; 84 | const end = x.ptr + x.length; 85 | 86 | while (ptr != end) { 87 | const ch = *ptr++; 88 | if (ch != '"') { 89 | app.put(ch); 90 | } else { 91 | app.put("""); // shorter than " 92 | } 93 | } 94 | } 95 | 96 | 97 | void writeHTMLEscaped(Flag!q{escapeQuotes} escapeQuotes, Appender)(ref Appender app, const(char)[] x) { 98 | auto ptr = x.ptr; 99 | const end = x.ptr + x.length; 100 | 101 | while (ptr != end) { 102 | const ch = *ptr++; 103 | switch (ch) { 104 | static if (escapeQuotes) { 105 | case '"': 106 | app.put("""); // shorter than " 107 | break; 108 | } 109 | case '<': 110 | app.put("<"); 111 | break; 112 | case '>': 113 | app.put(">"); 114 | break; 115 | case '&': 116 | app.put("&"); 117 | break; 118 | default: 119 | app.put(ch); 120 | break; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/html/parser.d: -------------------------------------------------------------------------------- 1 | module html.parser; 2 | 3 | 4 | import std.algorithm; 5 | import std.array; 6 | import std.ascii; 7 | import std.conv; 8 | import std.traits; 9 | 10 | import html.entities; 11 | import html.utils; 12 | 13 | 14 | private enum ParserStates { 15 | Text = 1, 16 | 17 | // tags 18 | PreTagName, 19 | TagName, 20 | SelfClosingTag, 21 | PreClosingTagName, 22 | ClosingTagName, 23 | PostClosingTagName, 24 | 25 | //attributes 26 | PreAttrName, 27 | AttrName, 28 | PostAttrName, 29 | PreAttrValue, 30 | AttrValueDQ, 31 | AttrValueSQ, 32 | AttrValueNQ, 33 | 34 | // decls 35 | PreDeclaration, 36 | Declaration, 37 | ProcessingInstruction, 38 | PreComment, 39 | Comment, 40 | PostComment1, 41 | PostComment2, 42 | 43 | // entities 44 | PreEntity, 45 | PreNumericEntity, 46 | NamedEntity, 47 | NumericEntity, 48 | HexEntity, 49 | 50 | // cdata 51 | PreCDATA, 52 | PreCDATA_C, 53 | PreCDATA_CD, 54 | PreCDATA_CDA, 55 | PreCDATA_CDAT, 56 | PreCDATA_CDATA, 57 | CDATA, 58 | PostCDATA1, 59 | PostCDATA2, 60 | 61 | // scripts / style 62 | PreScriptOrStyle, 63 | PreScript_SC, 64 | PreScript_SCR, 65 | PreScript_SCRI, 66 | PreScript_SCRIP, 67 | PreScript_SCRIPT, 68 | PreStyle_ST, 69 | PreStyle_STY, 70 | PreStyle_STYL, 71 | PreStyle_STYLE, 72 | PreClosingScriptOrStyle, 73 | ClosingScript_SC, 74 | ClosingScript_SCR, 75 | ClosingScript_SCRI, 76 | ClosingScript_SCRIP, 77 | ClosingScript_SCRIPT, 78 | ClosingStyle_ST, 79 | ClosingStyle_STY, 80 | ClosingStyle_STYL, 81 | ClosingStyle_STYLE, 82 | } 83 | 84 | 85 | private enum ParserTextStates { 86 | Normal = 0, 87 | Script, 88 | Style, 89 | } 90 | 91 | 92 | enum ParserOptions { 93 | ParseEntities = 1 << 0, 94 | DecodeEntities = 1 << 1, 95 | 96 | None = 0, 97 | Default = ParseEntities | DecodeEntities, 98 | } 99 | 100 | 101 | private auto parseNamedEntity(Handler, size_t options)(ref const(char)* start, ref const(char)* ptr, ref Handler handler) { 102 | auto length = ptr - start - 1; 103 | if (start[1+length-1] == ';') 104 | --length; 105 | 106 | if (!length) 107 | return false; 108 | 109 | auto limit = min(1+MaxLegacyEntityNameLength, length); 110 | auto name = start[1..1+length]; 111 | 112 | while (true) { 113 | if (auto pindex = name in index_) { 114 | handler.onNamedEntity(name); 115 | static if ((options & ParserOptions.DecodeEntities) != 0) { 116 | auto offset = codeOffset(*pindex); 117 | handler.onEntity(name, cast(const(char)[])bytes_[offset..offset + codeLength(*pindex)]); 118 | } 119 | 120 | start += 1 + name.length; 121 | return true; 122 | } else { 123 | if (limit <= MinEntityNameLength) 124 | return false; 125 | --limit; 126 | name = start[1..1+limit]; 127 | continue; 128 | } 129 | } 130 | } 131 | 132 | private auto parseNumericEntity(Handler, size_t options)(ref const(char)* start, ref const(char)* ptr, ref Handler handler) { 133 | auto length = (ptr - start) - 2; 134 | if (start[1+length-1] == ';') 135 | --length; 136 | 137 | if (!length) 138 | return false; 139 | auto name = start[2..2+length]; 140 | handler.onNumericEntity(name); 141 | 142 | static if ((options & ParserOptions.DecodeEntities) != 0) { 143 | auto cname = name[0..min(8, name.length)]; 144 | auto code = parse!uint(cname, 10); 145 | handler.onEntity(start[2..2+length], decodeCodePoint(code)); 146 | } 147 | 148 | start = ptr; 149 | return true; 150 | } 151 | 152 | private auto parseHexEntity(Handler, size_t options)(ref const(char)* start, ref const(char)* ptr, ref Handler handler) { 153 | auto length = (ptr - start) - 3; 154 | if (start[1+length-1] == ';') 155 | --length; 156 | 157 | if (!length) 158 | return false; 159 | 160 | auto name = start[3..3+length]; 161 | handler.onHexEntity(name); 162 | 163 | static if ((options & ParserOptions.DecodeEntities) != 0) { 164 | auto cname = name[0..min(6, name.length)]; 165 | auto code = parse!uint(cname, 16); 166 | handler.onEntity(name, decodeCodePoint(code)); 167 | } 168 | 169 | start = ptr; 170 | return true; 171 | } 172 | 173 | void parseHTML(Handler, size_t options = ParserOptions.Default)(const(char)[] source, ref Handler handler) { 174 | auto ptr = source.ptr; 175 | auto end = source.ptr + source.length; 176 | auto start = ptr; 177 | 178 | ParserStates state = ParserStates.Text; 179 | ParserStates saved = ParserStates.Text; 180 | ParserTextStates textState = ParserTextStates.Normal; 181 | 182 | enum ParseEntities = (options & (ParserOptions.ParseEntities | ParserOptions.DecodeEntities)) != 0; 183 | enum DecodeEntities = (options & ParserOptions.DecodeEntities) != 0; 184 | 185 | while (ptr != end) { 186 | final switch(state) with (ParserStates) { 187 | case Text: 188 | final switch (textState) with (ParserTextStates) { 189 | case Normal: 190 | static if (ParseEntities) { 191 | while ((ptr != end) && (*ptr != '<') && (*ptr != '&')) 192 | ++ptr; 193 | } else { 194 | while ((ptr != end) && (*ptr != '<')) 195 | ++ptr; 196 | } 197 | break; 198 | case Script: 199 | case Style: 200 | while ((ptr != end) && ((*ptr != '<') || (ptr + 1 == end) || (*(ptr + 1) != '/'))) 201 | ++ptr; 202 | } 203 | if (ptr == end) 204 | continue; 205 | 206 | static if (ParseEntities) { 207 | auto noEntity = *ptr != '&'; 208 | } else { 209 | enum noEntity = true; 210 | } 211 | 212 | if (noEntity) { 213 | if (start != ptr) 214 | handler.onText(start[0..ptr-start]); 215 | state = PreTagName; 216 | start = ptr; 217 | } else { 218 | static if (ParseEntities) { 219 | if (start != ptr) 220 | handler.onText(start[0..ptr-start]); 221 | saved = state; 222 | state = PreEntity; 223 | start = ptr; 224 | } 225 | } 226 | break; 227 | 228 | case PreTagName: 229 | if (*ptr == '/') { 230 | state = PreClosingTagName; 231 | } else if ((*ptr == '>') || isWhite(*ptr) || (textState != ParserTextStates.Normal)) { 232 | state = Text; 233 | } else { 234 | switch (*ptr) { 235 | case '!': 236 | state = PreDeclaration; 237 | start = ptr + 1; 238 | break; 239 | case '?': 240 | state = ProcessingInstruction; 241 | start = ptr + 1; 242 | break; 243 | case '<': 244 | handler.onText(start[0..ptr-start]); 245 | start = ptr + 1; 246 | break; 247 | default: 248 | if ((*ptr == 's') || (*ptr == 'S')) { 249 | state = PreScriptOrStyle; 250 | } else { 251 | state = TagName; 252 | } 253 | start = ptr; 254 | break; 255 | } 256 | } 257 | break; 258 | 259 | case PreScriptOrStyle: 260 | if ((*ptr == 'c') || (*ptr == 'C')) { 261 | state = PreScript_SC; 262 | } else if ((*ptr == 't') || (*ptr == 'T')) { 263 | state = PreStyle_ST; 264 | } else { 265 | state = TagName; 266 | goto case TagName; 267 | } 268 | break; 269 | 270 | case TagName: 271 | while ((ptr != end) && (*ptr != '/') && (*ptr != '>') && !isWhite(*ptr)) 272 | ++ptr; 273 | if (ptr == end) 274 | continue; 275 | 276 | handler.onOpenStart(start[0..ptr-start]); 277 | state = PreAttrName; 278 | continue; 279 | 280 | case PreClosingTagName: 281 | while ((ptr != end) && isWhite(*ptr)) 282 | ++ptr; 283 | if (ptr == end) 284 | continue; 285 | 286 | if (*ptr == '>') { 287 | state = Text; 288 | } else if (textState != ParserTextStates.Normal) { 289 | if ((*ptr == 's') || (*ptr == 'S')) { 290 | state = PreClosingScriptOrStyle; 291 | } else { 292 | state = Text; 293 | continue; 294 | } 295 | } else { 296 | state = ClosingTagName; 297 | start = ptr; 298 | } 299 | break; 300 | 301 | case PreClosingScriptOrStyle: 302 | if ((textState == ParserTextStates.Script) && ((*ptr == 'c') || (*ptr == 'C'))) { 303 | state = ClosingScript_SC; 304 | } else if ((textState == ParserTextStates.Style) && ((*ptr == 't') || (*ptr == 'T'))) { 305 | state = ClosingStyle_ST; 306 | } else { 307 | state = Text; 308 | goto case Text; 309 | } 310 | break; 311 | 312 | case ClosingTagName: 313 | while ((ptr != end) && (*ptr != '>') && !isWhite(*ptr)) 314 | ++ptr; 315 | if (ptr == end) 316 | continue; 317 | 318 | handler.onClose(start[0..ptr-start]); 319 | state = PostClosingTagName; 320 | continue; 321 | 322 | case PostClosingTagName: 323 | while ((ptr != end) && (*ptr != '>')) 324 | ++ptr; 325 | if (ptr == end) 326 | continue; 327 | 328 | state = Text; 329 | start = ptr + 1; 330 | break; 331 | 332 | case SelfClosingTag: 333 | while ((ptr != end) && (*ptr != '>') && isWhite(*ptr)) 334 | ++ptr; 335 | if (ptr == end) 336 | continue; 337 | 338 | if (*ptr == '>') { 339 | handler.onSelfClosing(); 340 | state = Text; 341 | start = ptr + 1; 342 | } else { 343 | state = PreAttrName; 344 | continue; 345 | } 346 | break; 347 | 348 | case PreAttrName: 349 | while ((ptr != end) && (*ptr != '>') && (*ptr != '/') && isWhite(*ptr)) 350 | ++ptr; 351 | if (ptr == end) 352 | continue; 353 | 354 | if (*ptr == '>') { 355 | handler.onOpenEnd(start[0..ptr-start]); 356 | state = Text; 357 | start = ptr + 1; 358 | } else if (*ptr == '/') { 359 | state = SelfClosingTag; 360 | } else { 361 | state = AttrName; 362 | start = ptr; 363 | } 364 | break; 365 | 366 | case AttrName: 367 | while ((ptr != end) && (*ptr != '=') && (*ptr != '>') && (*ptr != '/') && !isWhite(*ptr)) 368 | ++ptr; 369 | if (ptr == end) 370 | continue; 371 | 372 | handler.onAttrName(start[0..ptr-start]); 373 | state = PostAttrName; 374 | start = ptr; 375 | continue; 376 | 377 | case PostAttrName: 378 | while ((ptr != end) && (*ptr != '=') && (*ptr != '>') && (*ptr != '/') && isWhite(*ptr)) 379 | ++ptr; 380 | if (ptr == end) 381 | continue; 382 | 383 | switch(*ptr) { 384 | case '=': 385 | state = PreAttrValue; 386 | break; 387 | case '/': 388 | case '>': 389 | handler.onAttrEnd(); 390 | state = PreAttrName; 391 | continue; 392 | default: 393 | handler.onAttrEnd(); 394 | state = PreAttrName; 395 | start = ptr; 396 | continue; 397 | } 398 | break; 399 | 400 | case PreAttrValue: 401 | while ((ptr != end) && (*ptr != '\"') && (*ptr != '\'') && isWhite(*ptr)) 402 | ++ptr; 403 | if (ptr == end) 404 | continue; 405 | 406 | switch(*ptr) { 407 | case '\"': 408 | state = AttrValueDQ; 409 | start = ptr + 1; 410 | break; 411 | case '\'': 412 | state = AttrValueSQ; 413 | start = ptr + 1; 414 | break; 415 | default: 416 | state = AttrValueNQ; 417 | start = ptr; 418 | continue; 419 | } 420 | break; 421 | 422 | case AttrValueDQ: 423 | static if (ParseEntities) { 424 | while ((ptr != end) && (*ptr != '\"') && (*ptr != '&')) 425 | ++ptr; 426 | } else { 427 | while ((ptr != end) && (*ptr != '\"')) 428 | ++ptr; 429 | } 430 | if (ptr == end) 431 | continue; 432 | 433 | static if (ParseEntities) { 434 | auto noEntity = *ptr != '&'; 435 | } else { 436 | enum noEntity = true; 437 | } 438 | 439 | if (noEntity) { 440 | handler.onAttrValue(start[0..ptr-start]); 441 | handler.onAttrEnd(); 442 | state = PreAttrName; 443 | } else { 444 | static if (ParseEntities) { 445 | if (start != ptr) 446 | handler.onAttrValue(start[0..ptr-start]); 447 | saved = state; 448 | state = PreEntity; 449 | start = ptr; 450 | } 451 | } 452 | break; 453 | 454 | case AttrValueSQ: 455 | static if (ParseEntities) { 456 | while ((ptr != end) && (*ptr != '\'') && (*ptr != '&')) 457 | ++ptr; 458 | } else { 459 | while ((ptr != end) && (*ptr != '\'')) 460 | ++ptr; 461 | } 462 | if (ptr == end) 463 | continue; 464 | 465 | static if (ParseEntities) { 466 | auto noEntity = *ptr != '&'; 467 | } else { 468 | enum noEntity = true; 469 | } 470 | 471 | if (noEntity) { 472 | handler.onAttrValue(start[0..ptr-start]); 473 | handler.onAttrEnd(); 474 | state = PreAttrName; 475 | } else { 476 | static if (ParseEntities) { 477 | if (start != ptr) 478 | handler.onAttrValue(start[0..ptr-start]); 479 | saved = state; 480 | state = PreEntity; 481 | start = ptr; 482 | } 483 | } 484 | break; 485 | 486 | case AttrValueNQ: 487 | static if (ParseEntities) { 488 | while ((ptr != end) && (*ptr != '>') && (*ptr != '&') && !isWhite(*ptr)) 489 | ++ptr; 490 | } else { 491 | while ((ptr != end) && (*ptr != '>') && !isWhite(*ptr)) 492 | ++ptr; 493 | } 494 | if (ptr == end) 495 | continue; 496 | 497 | static if (ParseEntities) { 498 | auto noEntity = *ptr != '&'; 499 | } else { 500 | enum noEntity = true; 501 | } 502 | 503 | if (noEntity) { 504 | handler.onAttrValue(start[0..ptr-start]); 505 | handler.onAttrEnd(); 506 | state = PreAttrName; 507 | } else { 508 | static if (ParseEntities) { 509 | if (start != ptr) 510 | handler.onAttrValue(start[0..ptr-start]); 511 | saved = state; 512 | state = PreEntity; 513 | start = ptr; 514 | break; 515 | } 516 | } 517 | continue; 518 | 519 | case PreComment: 520 | if (*ptr == '-') { 521 | state = Comment; 522 | start = ptr + 1; 523 | } else { 524 | state = Declaration; 525 | } 526 | break; 527 | 528 | case Comment: 529 | while ((ptr != end) && (*ptr != '-')) 530 | ++ptr; 531 | if (ptr == end) 532 | continue; 533 | 534 | state = PostComment1; 535 | break; 536 | 537 | case PostComment1: 538 | state = (*ptr == '-') ? PostComment2 : Comment; 539 | break; 540 | 541 | case PostComment2: 542 | if (*ptr == '>') { 543 | handler.onComment(start[0..ptr-start-2]); 544 | state = Text; 545 | start = ptr + 1; 546 | } else if (*ptr != '-') { 547 | state = Comment; 548 | } 549 | break; 550 | 551 | case PreDeclaration: 552 | switch(*ptr) { 553 | case '[': 554 | state = PreCDATA; 555 | break; 556 | case '-': 557 | state = PreComment; 558 | break; 559 | default: 560 | state = Declaration; 561 | break; 562 | } 563 | break; 564 | 565 | case PreCDATA: 566 | if ((*ptr == 'C') || (*ptr == 'c')) { 567 | state = PreCDATA_C; 568 | } else { 569 | state = Declaration; 570 | continue; 571 | } 572 | break; 573 | 574 | case PreCDATA_C: 575 | if ((*ptr == 'D') || (*ptr == 'd')) { 576 | state = PreCDATA_CD; 577 | } else { 578 | state = Declaration; 579 | continue; 580 | } 581 | break; 582 | 583 | case PreCDATA_CD: 584 | if ((*ptr == 'A') || (*ptr == 'a')) { 585 | state = PreCDATA_CDA; 586 | } else { 587 | state = Declaration; 588 | continue; 589 | } 590 | break; 591 | 592 | case PreCDATA_CDA: 593 | if ((*ptr == 'T') || (*ptr == 't')) { 594 | state = PreCDATA_CDAT; 595 | } else { 596 | state = Declaration; 597 | continue; 598 | } 599 | break; 600 | 601 | case PreCDATA_CDAT: 602 | if ((*ptr == 'A') || (*ptr == 'a')) { 603 | state = PreCDATA_CDATA; 604 | } else { 605 | state = Declaration; 606 | continue; 607 | } 608 | break; 609 | 610 | case PreCDATA_CDATA: 611 | if (*ptr == '[') { 612 | state = CDATA; 613 | start = ptr + 1; 614 | } else { 615 | state = Declaration; 616 | continue; 617 | } 618 | break; 619 | 620 | case CDATA: 621 | while ((ptr != end) && (*ptr != ']')) 622 | ++ptr; 623 | if (ptr == end) 624 | continue; 625 | 626 | state = PostCDATA1; 627 | break; 628 | 629 | case PostCDATA1: 630 | state = (*ptr == ']') ? PostCDATA2 : CDATA; 631 | break; 632 | 633 | case PostCDATA2: 634 | if (*ptr == '>') { 635 | handler.onCDATA(start[0..ptr-start-2]); 636 | state = Text; 637 | start = ptr + 1; 638 | } else if (*ptr != ']') { 639 | state = CDATA; 640 | } 641 | break; 642 | 643 | case Declaration: 644 | while ((ptr != end) && (*ptr != '>')) 645 | ++ptr; 646 | if (ptr == end) 647 | continue; 648 | 649 | handler.onDeclaration(start[0..ptr-start]); 650 | state = Text; 651 | start = ptr + 1; 652 | break; 653 | 654 | case ProcessingInstruction: 655 | while ((ptr != end) && (*ptr != '>')) 656 | ++ptr; 657 | if (ptr == end) 658 | continue; 659 | 660 | handler.onProcessingInstruction(start[0..ptr-start]); 661 | state = Text; 662 | start = ptr + 1; 663 | break; 664 | 665 | case PreEntity: 666 | static if (ParseEntities) { 667 | if (*ptr == '#') { 668 | state = PreNumericEntity; 669 | break; 670 | } else { 671 | state = NamedEntity; 672 | continue; 673 | } 674 | } else { 675 | assert(0, "should never get here!"); 676 | } 677 | 678 | case NamedEntity: 679 | static if (ParseEntities) { 680 | while ((ptr != end) && (*ptr != ';') && isAlphaNum(*ptr) && (ptr - start < MaxEntityNameLength)) 681 | ++ptr; 682 | if (ptr == end) 683 | continue; 684 | 685 | if ((saved == Text) || (*ptr != '=')) { 686 | if (parseNamedEntity!(Handler, options)(start, ptr, handler)) { 687 | if (*start == ';') 688 | ++start; 689 | } 690 | } 691 | state = saved; 692 | 693 | if (*ptr == ';') break; 694 | else continue; 695 | } else { 696 | break; 697 | } 698 | 699 | case PreNumericEntity: 700 | static if (ParseEntities) { 701 | if ((*ptr == 'X') || (*ptr == 'x')) { 702 | state = HexEntity; 703 | break; 704 | } else { 705 | state = NumericEntity; 706 | continue; 707 | } 708 | } else { 709 | assert(0, "should never get here!"); 710 | } 711 | 712 | case NumericEntity: 713 | static if (ParseEntities) { 714 | while ((ptr != end) && (*ptr != ';') && isDigit(*ptr)) 715 | ++ptr; 716 | if (ptr == end) 717 | continue; 718 | 719 | state = saved; 720 | if (parseNumericEntity!(Handler, options)(start, ptr, handler)) { 721 | if (*start == ';') 722 | ++start; 723 | } 724 | 725 | if (*ptr == ';') break; 726 | else continue; 727 | } else { 728 | break; 729 | } 730 | 731 | case HexEntity: 732 | static if (ParseEntities) { 733 | while ((ptr != end) && (*ptr != ';') && isHexDigit(*ptr)) 734 | ++ptr; 735 | if (ptr == end) 736 | continue; 737 | 738 | state = saved; 739 | if (parseHexEntity!(Handler, options)(start, ptr, handler)) { 740 | if (*start == ';') 741 | ++start; 742 | } 743 | 744 | if (*ptr == ';') break; 745 | else continue; 746 | } else { 747 | break; 748 | } 749 | 750 | case PreScript_SC: 751 | if ((*ptr == 'r') || (*ptr == 'R')) { 752 | state = PreScript_SCR; 753 | } else { 754 | state = TagName; 755 | goto case TagName; 756 | } 757 | break; 758 | 759 | case PreScript_SCR: 760 | if ((*ptr == 'i') || (*ptr == 'I')) { 761 | state = PreScript_SCRI; 762 | } else { 763 | state = TagName; 764 | goto case TagName; 765 | } 766 | break; 767 | 768 | case PreScript_SCRI: 769 | if ((*ptr == 'p') || (*ptr == 'p')) { 770 | state = PreScript_SCRIP; 771 | } else { 772 | state = TagName; 773 | goto case TagName; 774 | } 775 | break; 776 | 777 | case PreScript_SCRIP: 778 | if ((*ptr == 't') || (*ptr == 't')) { 779 | state = PreScript_SCRIPT; 780 | } else { 781 | state = TagName; 782 | goto case TagName; 783 | } 784 | break; 785 | 786 | case PreScript_SCRIPT: 787 | if ((*ptr == '/') || (*ptr == '>') || isWhite(*ptr)) 788 | textState = ParserTextStates.Script; 789 | 790 | state = TagName; 791 | goto case TagName; 792 | 793 | case PreStyle_ST: 794 | if ((*ptr == 'y') || (*ptr == 'Y')) { 795 | state = PreStyle_STY; 796 | } else { 797 | state = TagName; 798 | goto case TagName; 799 | } 800 | break; 801 | 802 | case PreStyle_STY: 803 | if ((*ptr == 'l') || (*ptr == 'L')) { 804 | state = PreStyle_STYL; 805 | } else { 806 | state = TagName; 807 | goto case TagName; 808 | } 809 | break; 810 | 811 | case PreStyle_STYL: 812 | if ((*ptr == 'e') || (*ptr == 'E')) { 813 | state = PreStyle_STYLE; 814 | } else { 815 | state = TagName; 816 | goto case TagName; 817 | } 818 | break; 819 | 820 | case PreStyle_STYLE: 821 | if ((*ptr == '/') || (*ptr == '>') || isWhite(*ptr)) 822 | textState = ParserTextStates.Style; 823 | 824 | state = TagName; 825 | goto case TagName; 826 | 827 | case ClosingScript_SC: 828 | if ((*ptr == 'r') || (*ptr == 'R')) { 829 | state = ClosingScript_SCR; 830 | } else { 831 | state = Text; 832 | goto case Text; 833 | } 834 | break; 835 | 836 | case ClosingScript_SCR: 837 | if ((*ptr == 'i') || (*ptr == 'I')) { 838 | state = ClosingScript_SCRI; 839 | } else { 840 | state = Text; 841 | goto case Text; 842 | } 843 | break; 844 | 845 | case ClosingScript_SCRI: 846 | if ((*ptr == 'p') || (*ptr == 'p')) { 847 | state = ClosingScript_SCRIP; 848 | } else { 849 | state = Text; 850 | goto case Text; 851 | } 852 | break; 853 | 854 | case ClosingScript_SCRIP: 855 | if ((*ptr == 't') || (*ptr == 't')) { 856 | state = ClosingScript_SCRIPT; 857 | } else { 858 | state = Text; 859 | goto case Text; 860 | } 861 | break; 862 | 863 | case ClosingScript_SCRIPT: 864 | if ((*ptr == '>') || isWhite(*ptr)) { 865 | textState = ParserTextStates.Normal; 866 | state = ClosingTagName; 867 | start += 2; 868 | continue; 869 | } else { 870 | state = Text; 871 | goto case Text; 872 | } 873 | 874 | case ClosingStyle_ST: 875 | if ((*ptr == 'y') || (*ptr == 'Y')) { 876 | state = ClosingStyle_STY; 877 | } else { 878 | state = Text; 879 | goto case Text; 880 | } 881 | break; 882 | 883 | case ClosingStyle_STY: 884 | if ((*ptr == 'l') || (*ptr == 'L')) { 885 | state = ClosingStyle_STYL; 886 | } else { 887 | state = Text; 888 | goto case Text; 889 | } 890 | break; 891 | 892 | case ClosingStyle_STYL: 893 | if ((*ptr == 'e') || (*ptr == 'E')) { 894 | state = ClosingStyle_STYLE; 895 | } else { 896 | state = Text; 897 | goto case Text; 898 | } 899 | break; 900 | 901 | case ClosingStyle_STYLE: 902 | if ((*ptr == '>') || isWhite(*ptr)) { 903 | textState = ParserTextStates.Normal; 904 | state = ClosingTagName; 905 | start += 2; 906 | continue; 907 | } else { 908 | state = Text; 909 | goto case Text; 910 | } 911 | } 912 | 913 | ++ptr; 914 | } 915 | 916 | auto remaining = start[0..ptr-start]; 917 | if (!remaining.empty) { 918 | switch(state) with (ParserStates) { 919 | case Comment: 920 | handler.onComment(remaining); 921 | break; 922 | case PostComment1: 923 | handler.onComment(remaining[0..$-1]); 924 | break; 925 | case PostComment2: 926 | handler.onComment(remaining[0..$-2]); 927 | break; 928 | case NamedEntity: 929 | static if (ParseEntities) { 930 | if (saved == Text) { 931 | if (ptr-start > 1) 932 | parseNamedEntity!(Handler, options)(start, ptr, handler); 933 | if (start < ptr) 934 | handler.onText(start[0..ptr-start]); 935 | } 936 | } 937 | break; 938 | case NumericEntity: 939 | static if (ParseEntities) { 940 | if (saved == Text) { 941 | if (ptr-start > 2) 942 | parseNumericEntity!(Handler, options)(start, ptr, handler); 943 | if (start < ptr) 944 | handler.onText(start[0..ptr-start]); 945 | } 946 | } 947 | break; 948 | case HexEntity: 949 | static if (ParseEntities) { 950 | if (saved == Text) { 951 | if (ptr-start > 3) 952 | parseHexEntity!(Handler, options)(start, ptr, handler); 953 | if (start < ptr) 954 | handler.onText(start[0..ptr-start]); 955 | } 956 | } 957 | break; 958 | default: 959 | if ((state != TagName) && 960 | (state != PreAttrName) && 961 | (state != PreAttrValue) && 962 | (state != PostAttrName) && 963 | (state != AttrName) && 964 | (state != AttrValueDQ) && 965 | (state != AttrValueSQ) && 966 | (state != AttrValueNQ) && 967 | (state != ClosingTagName)) { 968 | handler.onText(remaining); 969 | } 970 | break; 971 | } 972 | } 973 | 974 | handler.onDocumentEnd(); 975 | } 976 | -------------------------------------------------------------------------------- /src/html/entities.d: -------------------------------------------------------------------------------- 1 | module html.entities; 2 | 3 | import std.algorithm; 4 | import std.conv; 5 | import std.utf; 6 | 7 | 8 | enum MinEntityNameLength = 2; 9 | enum MaxEntityNameLength = 31; 10 | enum MaxLegacyEntityNameLength = 6; 11 | 12 | 13 | package enum EntityIndexDef = `["nrtri":12307,"downharpoonright":22867,"harrw":42131,"Bernoullis":9587,"hArr":2035,"colon":44257,"TScy":2962,"rbrke":44547,"naturals":2739,"quatint":35571,"orarr":45459,"cupcup":6323,"coprod":7379,"ecir":6275,"Vopf":7428,"divide":10370,"DownTee":39427,"Umacr":47618,"block":48403,"Sacute":12354,"barwed":14307,"jcirc":49746,"GJcy":13730,"nrtrie":15027,"Succeeds":13635,"cupdot":16339,"horbar":17011,"NotSucceeds":12851,"Rarrtl":17427,"comma":52625,"thorn":52818,"rsaquo":19251,"Iogon":53298,"lvertneqq":23062,"NotNestedGreaterGreater":67269,"complement":34883,"scap":21603,"csube":55363,"vBar":21827,"rtimes":23539,"xuplus":24483,"Implies":24771,"approx":12083,"jmath":56850,"LeftRightArrow":19747,"ngeq":25059,"nges":25221,"ThickSpace":65446,"backprime":1315,"Ucirc":58834,"Backslash":14499,"RightVector":27187,"efDot":60451,"bigtriangleup":37507,"vArr":14979,"wedgeq":31395,"blacktriangle":54675,"Abreve":32418,"NotLeftTriangleEqual":3779,"Tcedil":32866,"iacute":33186,"Hscr":32483,"frac12":34114,"frac13":34339,"frac14":34418,"utrif":54675,"frac15":34595,"frac16":34691,"dopf":33956,"Atilde":35250,"frac18":35203,"edot":34386,"scnE":34739,"frac23":36003,"vsupnE":36502,"frac25":36451,"SupersetEqual":25587,"vrtri":38451,"frac34":37954,"frac35":38035,"lstrok":38322,"frac38":38547,"downharpoonleft":14931,"order":20515,"isinsv":39379,"mscr":38756,"frac45":39779,"parsl":57587,"NegativeThickSpace":2323,"vsupne":41078,"frac56":41363,"frac58":41539,"NegativeVeryThinSpace":2323,"csupe":68627,"frac78":43731,"there4":20163,"NotGreater":6675,"cirmid":44499,"circlearrowleft":11763,"NotPrecedesEqual":18997,"fjlig":4834,"cudarrl":64099,"TildeEqual":9939,"ngtr":6675,"cudarrr":64547,"iiota":7331,"Longrightarrow":25379,"blacktriangleleft":23203,"RightDownTeeVector":20803,"DoubleLeftArrow":28227,"ShortLeftArrow":40195,"Delta":13394,"nsmid":14259,"omicron":450,"bkarow":1267,"Mopf":50292,"nbumpe":9205,"rpar":50913,"trisb":17987,"Vscr":53764,"sect":43618,"LeftTriangle":24435,"ropf":54724,"sdot":55123,"subedot":8659,"barwedge":14307,"aelig":2178,"eqslantgtr":45299,"longrightarrow":19395,"CircleMinus":30547,"leftarrow":40195,"curvearrowleft":5459,"uwangle":16771,"zeetrf":14547,"caret":28419,"lneqq":11811,"uplus":29123,"trpezium":51875,"DoubleRightTee":30019,"imof":61219,"setmn":14499,"nwArr":30163,"LeftArrow":40195,"colone":39091,"DownRightVector":25299,"semi":62849,"nwarrow":23747,"OverBar":24003,"njcy":63186,"gtlPar":63427,"gtrsim":2867,"bcong":8131,"isinE":35155,"seArr":35827,"Hcirc":36178,"kappav":29458,"Cacute":65122,"ZHcy":64754,"nisd":65811,"Rcaron":67714,"Udblac":67970,"succeq":21235,"isins":41731,"thetav":29922,"isinv":3875,"hairsp":275,"varsigma":33154,"caron":11538,"Epsilon":32274,"NotRightTriangleEqual":15027,"acE":43653,"emacr":43954,"cularrp":33219,"dscr":2452,"kappa":44322,"TRADE":16387,"dscy":3922,"LJcy":4146,"cularr":5459,"Nacute":6882,"iocy":7682,"IJlig":46482,"weierp":8451,"LessSlantEqual":483,"acd":46579,"mumap":46915,"OpenCurlyQuote":51651,"wreath":9987,"precneqq":42995,"LeftUpVector":30499,"LeftTeeArrow":23443,"minusdu":40371,"SucceedsTilde":1507,"sext":11267,"prnsim":12611,"acy":48498,"emsp13":12803,"emsp14":13043,"NotGreaterLess":10995,"xcap":4051,"Wedge":35779,"lmidot":14674,"mapstoup":11043,"bullet":16483,"nspar":13987,"afr":51060,"vartriangleright":38451,"CHcy":17698,"Yacute":19218,"epsiv":25554,"LeftTee":45667,"LowerRightArrow":29795,"ssetmn":14499,"HumpEqual":9203,"ecirc":7026,"Dopf":21652,"Edot":22194,"dsol":23651,"triangleq":18579,"fpartint":21971,"Mscr":25715,"apE":10579,"ImaginaryI":10899,"InvisibleComma":33379,"iopf":26660,"and":41315,"amp":17777,"ang":12899,"RightTeeVector":44451,"pound":7810,"nldr":29219,"bcy":60722,"uharl":30499,"ape":33427,"uharr":45203,"DoubleLongLeftRightArrow":35331,"rscr":30388,"nLeftarrow":15939,"nleq":30451,"nles":30677,"shcy":31362,"YUcy":31522,"iota":31602,"scaron":32690,"hellip":33027,"TildeFullEqual":47779,"bfr":62788,"submult":56419,"DownArrowUpArrow":6131,"DownArrow":36883,"xlarr":36723,"DD":46307,"ast":64369,"integers":33715,"uhblk":64883,"iiiint":2403,"Gbreve":38722,"oacute":39474,"ubrcy":66434,"ncongdot":38869,"cap":323,"dArr":37027,"Cayleys":16627,"GT":40289,"bne":884,"ffllig":43987,"xcup":37411,"dtri":13315,"npreceq":18997,"lesseqgtr":3,"Gg":59779,"nsupseteq":1059,"swarrow":17731,"Ropf":4387,"looparrowleft":1459,"bot":2787,"Product":46771,"Gt":5875,"iinfin":47059,"zacute":47234,"aogon":8338,"harrcir":65539,"breve":9394,"Im":27907,"cfr":9524,"rsqb":47409,"NotGreaterTilde":17331,"rrarr":9891,"dotminus":6051,"LT":46289,"eqcirc":6275,"wopf":48612,"chi":11634,"divonx":47923,"nmid":14259,"tilde":12274,"oast":27315,"nesim":13093,"bigwedge":35779,"eqvparsl":48323,"Ll":64435,"cir":15171,"Lt":20851,"copysr":53027,"RightTriangleBar":17843,"Mu":66258,"ldquo":18899,"Nu":67346,"nvrArr":55011,"twixt":21459,"Or":68035,"Pi":68258,"vnsub":22582,"sime":9939,"Pr":835,"simg":56211,"siml":56579,"Barwed":9155,"Re":2995,"vnsup":11366,"nparsl":57590,"dcy":24738,"HARDcy":57922,"succneqq":34739,"Sc":4739,"deg":25346,"CenterDot":7154,"trianglelefteq":23155,"longmapsto":11091,"gEl":27027,"intprod":16723,"dfr":27748,"cuepr":28275,"rArr":24771,"ForAll":14179,"larrbfs":18627,"NoBreak":19587,"rtri":22099,"backsim":20755,"die":11458,"cuesc":6915,"SucceedsSlantEqual":4995,"Dscr":61748,"ocir":62371,"NotSucceedsTilde":1509,"div":10370,"Xi":15138,"times":12738,"cup":29555,"igrave":48962,"plusacir":62099,"gneqq":3955,"iscr":64484,"Integral":3411,"umacr":39186,"lbrksld":27955,"Equal":40547,"subnE":26211,"ecy":41410,"lbrkslu":30067,"dot":14418,"asympeq":30931,"Iacute":242,"neArr":42371,"dollar":817,"Ccirc":42786,"dlcorn":1363,"dotplus":32723,"SquareSubset":33859,"mstpos":3571,"fflig":43827,"efr":44068,"subne":13459,"isin":3875,"leqslant":483,"minusb":5763,"iogon":45026,"ac":3571,"minusd":6051,"LessTilde":11171,"egs":45299,"Lstrok":6450,"af":28979,"LeftUpTeeVector":43347,"Lleftarrow":32115,"subset":8291,"ap":12083,"succcurlyeq":4995,"upsih":47202,"odiv":8755,"ijlig":47330,"isindot":39635,"Cconint":40243,"lozenge":22355,"dd":18211,"npart":48245,"gE":3043,"ccaps":48451,"mapstodown":47363,"Iopf":12500,"ee":34787,"eg":35283,"ell":49651,"subsim":14627,"npar":13987,"GreaterSlantEqual":25219,"el":35875,"LeftDownTeeVector":46867,"els":23955,"DownRightTeeVector":14083,"ucirc":14850,"leftleftarrows":12131,"eng":51346,"ge":21923,"parallel":1795,"gg":5875,"ccaron":18178,"nsubE":43877,"Rscr":17379,"gl":33475,"qprime":18851,"Iota":19122,"nopf":19156,"gt":40289,"fcy":53522,"TripleDot":10275,"odot":19923,"ic":33379,"lE":17603,"RightDownVector":22867,"ii":10899,"erarr":54963,"in":3875,"it":9107,"nsube":53715,"ffr":56356,"varsubsetneq":13462,"ReverseElement":18435,"wscr":23588,"nsucc":12851,"loang":57091,"le":1987,"COPY":4914,"lg":15619,"eta":57682,"ll":20851,"Vdashl":26435,"eth":37874,"exponentiale":34787,"lt":46289,"oS":27107,"lessapprox":61107,"mp":36259,"ne":46675,"ncaron":28546,"ni":18435,"mu":47266,"UpperRightArrow":4099,"disin":60403,"circledR":26994,"nu":48018,"circledS":27107,"or":16115,"DownTeeArrow":47363,"subsub":31555,"pi":48882,"SOFTcy":31874,"gap":8499,"pm":49570,"loarr":62147,"mdash":62323,"NotNestedLessLess":59109,"pr":31635,"subsup":33523,"supset":11363,"gesdotol":31683,"sc":13635,"xwedge":35779,"SubsetEqual":10803,"gcy":63970,"emsp":36083,"rx":53331,"angmsd":37907,"gel":5187,"leftthreetimes":42883,"geq":21923,"RightArrowBar":26083,"ges":25219,"napid":65637,"npre":18997,"Wopf":38996,"nsupE":30597,"supsim":40499,"ggg":59779,"gfr":66468,"lesdot":41683,"delta":67570,"Colone":42531,"lsime":67923,"lsimg":68131,"gimel":68179,"glE":68435,"slarr":40195,"vartheta":29922,"wp":8451,"wr":9987,"xi":58130,"nsupe":1059,"GreaterEqualLess":5187,"uacute":44178,"smid":26483,"spadesuit":44899,"boxtimes":26723,"gnE":3955,"iuml":44994,"gla":5091,"bigsqcup":28835,"dzcy":45586,"THORN":5506,"lobrk":6771,"glj":6723,"inodot":46450,"tshcy":8050,"commat":47649,"napos":9314,"gne":9475,"lrtri":9795,"NotSupersetEqual":1059,"dlcrop":48195,"rarrbfs":67219,"bigcirc":19443,"ensp":49603,"kcedil":50498,"Superset":11363,"shchcy":52050,"boxbox":52003,"LeftVectorBar":34547,"supsub":52979,"hfr":17060,"NotSuperset":11366,"dHar":52499,"shortparallel":1795,"updownarrow":99,"RightUpVectorBar":17795,"NotElement":15219,"ddotseq":5411,"dbkarow":6483,"supsup":54211,"Leftarrow":28227,"tbrk":54259,"epar":54627,"rbrksld":9635,"hslash":6227,"OverBrace":36403,"smte":22915,"Iscr":6579,"Lmidot":57330,"rbrkslu":12563,"hcirc":25186,"eopf":57796,"realpart":2995,"UnderBar":12481,"nsce":21237,"forkv":27859,"diamond":16963,"hercon":60755,"udarr":26579,"nscr":60340,"ogon":61074,"triminus":52451,"icy":30322,"LeftVector":20307,"blacktriangleright":29843,"clubsuit":8979,"lAarr":32115,"dfisht":62899,"iff":2035,"lEg":15699,"DownBreve":5330,"ifr":34020,"xharr":25427,"diamondsuit":1891,"rightrightarrows":9891,"Uring":37218,"lesges":65187,"Scirc":37378,"ntilde":55314,"nsim":56259,"EmptyVerySmallSquare":48787,"ograve":55410,"intcal":58739,"wedge":41315,"Scaron":68546,"InvisibleTimes":9107,"imagpart":27907,"DoubleUpDownArrow":14979,"dotsquare":52339,"complexes":59379,"rHar":147,"solb":723,"pitchfork":19971,"Nopf":2739,"bigcap":4051,"spar":1795,"epsi":5698,"int":3411,"PlusMinus":49570,"jcy":46114,"Oacute":8018,"propto":7971,"Wscr":8228,"vBarv":47011,"fallingdotseq":60451,"sopf":9732,"Upsilon":39858,"tdot":10275,"jfr":48132,"ntgl":10995,"RightTee":21187,"yacy":12770,"hookrightarrow":28691,"strns":14226,"backsimeq":8931,"nsub":14883,"leftrightarrows":14371,"nsup":17203,"SuchThat":18435,"ZeroWidthSpace":2323,"Zacute":20578,"HumpDownHump":2915,"ntlg":19875,"supsetneqq":24387,"ulcorn":21315,"angmsdaa":18531,"angmsdab":18675,"angmsdac":19075,"angmsdad":19347,"angmsdae":19539,"ltrPar":23395,"angmsdaf":19795,"angmsdag":20067,"angmsdah":20259,"DownLeftTeeVector":18947,"Downarrow":37027,"succsim":1507,"oint":24579,"OElig":57410,"kcy":58002,"DotDot":26851,"RightUpDownVector":56739,"xmap":11091,"vartriangleleft":24435,"homtht":27811,"xsqcup":28835,"SucceedsEqual":21235,"compfn":29171,"kfr":60212,"eacute":29762,"SquareSuperset":3251,"nGg":61413,"nGt":62470,"lfloor":33107,"Iuml":32450,"tcaron":33682,"hstrok":34082,"rationals":16531,"dtdot":63219,"escr":35107,"gesdot":37123,"bigcup":37411,"Dashv":48051,"therefore":20163,"nvap":37558,"UpTeeArrow":11043,"lap":61107,"lat":65715,"nLl":65861,"nLt":66790,"SquareSupersetEqual":6179,"lgE":67411,"varpi":67090,"lcy":68226,"sqcap":5235,"leg":3,"DoubleVerticalBar":1795,"Igrave":43586,"esim":5363,"ltcir":1667,"leq":1987,"les":483,"ngeqq":3045,"nvge":44358,"reals":4387,"lfr":4644,"sccue":4995,"eqsim":5363,"nvgt":45860,"Rrightarrow":1107,"natur":7763,"gsime":7923,"ntrianglelefteq":3779,"plusdo":32723,"nlarr":8707,"NegativeThinSpace":2323,"gsiml":9059,"ContourIntegral":24579,"andand":47667,"plusdu":47971,"lmoustache":4003,"Eopf":47828,"check":10947,"napprox":32339,"lesseqqgtr":15699,"lnE":11811,"Congruent":39683,"squf":27667,"nvle":49158,"notin":15219,"map":15459,"nvlt":50628,"suphsol":1715,"Nscr":50852,"xnis":51603,"angsph":52691,"jopf":52084,"lne":2131,"iexcl":17570,"lnsim":18355,"micro":18402,"Subset":53827,"UpArrow":5539,"xcirc":19443,"twoheadrightarrow":46723,"mcy":21042,"curvearrowright":29363,"expectation":22675,"sscr":55508,"loz":22355,"Gamma":22834,"notni":23347,"suphsub":11715,"mfr":24276,"plusmn":49570,"lrm":25139,"lsh":25763,"lambda":58626,"shortmid":26483,"Cedilla":15266,"mho":26531,"mid":26483,"NotGreaterEqual":25059,"andd":58867,"Ccaron":59426,"leftharpoondown":16851,"capand":59539,"ulcrop":59587,"bsemi":27395,"IEcy":59906,"andv":60499,"nwnear":61027,"mnplus":36259,"ccirc":31170,"pointint":53427,"ltdot":31123,"ange":61859,"QUOT":36865,"bdquo":21363,"bbrk":60931,"itilde":62866,"nap":32339,"ExponentialE":34787,"sqcup":32531,"EmptySmallSquare":51011,"star":63475,"ngE":3045,"realine":17379,"lagran":23251,"ncy":37250,"Sopf":64692,"Ncaron":65234,"Leftrightarrow":2035,"para":59874,"sqsubseteq":15859,"precnsim":12611,"ultri":39987,"asymp":12083,"ltlarr":67123,"nge":25059,"sharp":40979,"nfr":40708,"Prime":41955,"part":29411,"DJcy":68338,"equiv":39683,"ngt":6675,"capcap":531,"xopf":68484,"omid":68675,"udhar":13219,"nlE":43269,"Supset":2563,"profalar":61811,"euml":2370,"bowtie":3667,"rightarrowtail":46019,"gtrless":33475,"Vvdash":4339,"ugrave":4530,"nis":44707,"niv":18435,"subE":6371,"nle":30451,"ufisht":8403,"NewLine":37649,"varsupsetneq":41078,"NotSucceedsEqual":21237,"ruluhar":39299,"nlt":24531,"curarrm":41235,"sube":10803,"Iukcy":48674,"euro":12691,"lescc":49299,"succ":13635,"leftrightarrow":19747,"Uacute":14754,"bernou":9587,"ocy":50098,"nleqslant":30677,"longleftrightarrow":25427,"sqsube":15859,"ShortUpArrow":5539,"not":47890,"tint":16163,"npr":51,"cupor":52643,"dcaron":19842,"ofr":53236,"thksim":14131,"nsc":12851,"sbquo":54307,"ogt":54467,"upharpoonright":45203,"ohm":35970,"flat":22435,"Escr":22675,"PartialD":29411,"aopf":23892,"num":57025,"Kcedil":25522,"RightArrow":7059,"subseteq":10803,"nparallel":13987,"UpEquilibrium":13219,"supedot":50243,"oelig":58594,"heartsuit":54835,"sup1":27074,"sup2":27234,"sup3":27362,"jscr":27492,"NotGreaterSlantEqual":25221,"par":1795,"olt":59667,"khcy":28514,"curarr":29363,"apid":29027,"supE":29651,"nsucceq":21237,"Esim":30211,"sung":31283,"conint":24579,"pcy":62194,"capcup":34931,"supe":25587,"bumpeq":9203,"lsquo":51651,"VerticalSeparator":46819,"ord":63379,"pfr":63668,"numero":36675,"lbarr":64179,"approxeq":33427,"sqsupe":6179,"Sqrt":8179,"DoubleContourIntegral":63555,"nleqq":43269,"phi":64850,"orv":65075,"Union":37411,"Amacr":65330,"UpTee":2787,"rfloor":4595,"zcaron":39954,"trianglerighteq":3619,"apos":40641,"nless":24531,"eDot":40659,"piv":67090,"excl":40913,"rightharpoonup":27187,"PrecedesSlantEqual":20019,"jsercy":42754,"capdot":43043,"Barv":42707,"maltese":55795,"Jopf":42932,"opar":43539,"blacklozenge":49347,"Because":7283,"beta":44866,"Laplacetrf":23251,"Ntilde":45922,"beth":45507,"Sscr":45956,"prE":6627,"oopf":46516,"Ograve":47298,"gtcir":10403,"langd":10851,"empty":11667,"pre":11859,"lbbrk":12035,"qfr":12212,"xscr":49044,"succnsim":36211,"Acirc":13906,"psi":14594,"nGtv":5877,"lcedil":51378,"bsime":8931,"AMP":17777,"precnapprox":20707,"xrarr":19395,"prcue":20019,"lnapprox":51747,"telrec":55219,"nsubseteqq":43877,"RightDownVectorBar":20355,"Equilibrium":16067,"Star":56019,"ofcir":23299,"kjcy":56658,"gnsim":25635,"rightsquigarrow":13827,"uring":25810,"scirc":25954,"rdquo":26339,"rcy":26626,"reg":26994,"intercal":58739,"Xopf":60660,"rfr":29956,"egrave":61586,"nshortparallel":13987,"perp":2787,"nsccue":4867,"crarr":31027,"Euml":61618,"yicy":62018,"rho":32386,"thetasym":29922,"ascr":62948,"uarr":5539,"square":44211,"squarf":27667,"Imacr":35074,"scE":36131,"fnof":64146,"Longleftrightarrow":35331,"xutri":37507,"gtdot":38403,"otilde":63522,"supmult":27555,"Eacute":63602,"rlm":38947,"gesles":66563,"lsquor":54307,"pertenk":28179,"llhard":66707,"rightharpoondown":25299,"sce":21235,"GreaterLess":33475,"ordf":63762,"Tcaron":642,"ordm":63842,"Hstrok":1154,"bprime":1315,"szlig":35746,"supnE":24387,"scy":43186,"aleph":43395,"Exists":4243,"HorizontalLine":57139,"sfr":45796,"nharr":46243,"supne":41075,"rsh":46627,"Aopf":8596,"Icirc":36370,"subsetneqq":26211,"drcorn":11219,"sim":14131,"shy":48098,"profsurf":66659,"drbkarow":9683,"Jscr":13764,"tridot":15507,"fopf":15076,"Vdash":50579,"gdot":15666,"gesdoto":44131,"CircleDot":19923,"apacir":17891,"duarr":6131,"tau":52546,"LeftTriangleBar":15987,"epsilon":5698,"awconint":771,"Acy":53202,"esdot":1587,"smt":53587,"precsim":37171,"otimes":10147,"fork":19971,"oscr":20515,"gescc":54787,"sol":55345,"oror":21875,"ecolon":22723,"tcy":55442,"supdsub":47475,"Afr":56068,"triangledown":13315,"lBarr":56531,"toea":23795,"cuvee":17939,"plusb":57491,"lopar":57715,"tfr":57860,"pluse":57955,"LeftAngleBracket":32035,"squ":44211,"dashv":45667,"xvee":20659,"nequiv":28099,"fllig":59283,"lurdshar":25907,"sub":8291,"phiv":3458,"sum":61267,"sup":11363,"centerdot":7154,"CupCap":30931,"phone":61971,"roang":62051,"curlyeqsucc":6915,"lang":32035,"Beta":32194,"And":62659,"Alpha":62754,"ffilig":33267,"ccupssm":56691,"bigotimes":20211,"Lambda":35714,"Oopf":35508,"Bcy":63634,"geqq":3043,"RightVectorBar":44275,"nsubset":22582,"osol":37459,"gesl":38086,"ucy":65410,"quest":65617,"top":39427,"Bfr":66036,"roarr":66211,"Eogon":66530,"Xscr":39892,"larr":40195,"rnmid":67043,"cuwed":40931,"topf":41172,"LowerLeftArrow":17731,"late":34147,"Itilde":42418,"ufr":67780,"xlArr":38355,"Intersection":4051,"varkappa":29458,"nvltrie":62230,"nprec":51,"auml":42674,"GreaterGreater":40083,"tstrok":44754,"tosa":35027,"parsim":44819,"orderof":20515,"Uparrow":10499,"UpperLeftArrow":23747,"laquo":5586,"scnap":5651,"ENG":5730,"larrsim":64291,"Cap":7235,"radic":8179,"clubs":8979,"gamma":9282,"questeq":50531,"Mellintrf":25715,"hyphen":36979,"uml":11458,"Hacek":11538,"numsp":11939,"TSHcy":13426,"Ugrave":50978,"eparsl":51299,"vcy":15794,"robrk":8803,"vee":16115,"vangrt":52579,"Cfr":16627,"NotDoubleVerticalBar":13987,"ETH":16818,"Sigma":17122,"vzigzag":4291,"smallsetminus":14499,"bsolhsub":50355,"larrb":18035,"Chi":18770,"drcrop":54115,"vfr":20404,"Ascr":54516,"NotHumpEqual":9205,"Uarr":54915,"rcedil":55986,"nLtv":55573,"SquareIntersection":5235,"nLeftrightarrow":49955,"doteq":1587,"nsupset":11366,"NotRightTriangleBar":40421,"srarr":7059,"fscr":58196,"ldca":58499,"NJcy":58914,"equivDD":16579,"Dcaron":60098,"tritime":17523,"lesdoto":17475,"oslash":60578,"scpolint":50739,"ouml":60274,"RightTriangle":38451,"Dcy":29890,"prime":30115,"xotime":20211,"Del":31203,"geqslant":25219,"CounterClockwiseContourIntegral":771,"multimap":46915,"Dfr":33316,"lates":34150,"nshortmid":14259,"LessLess":59107,"DiacriticalDot":14418,"wfr":36804,"varphi":3458,"NotLessLess":55573,"lcub":50225,"bsolb":37827,"nleftrightarrow":46243,"DScy":65586,"mapsto":15459,"Cup":40035,"rAarr":1107,"sqsubset":33859,"DoubleDot":11458,"dblac":22482,"iukcy":40770,"bepsi":41282,"IOcy":67378,"Conint":63555,"ubreve":68002,"divideontimes":47923,"DoubleLeftTee":48051,"Fopf":67604,"VDash":41779,"trie":18579,"Gdot":67842,"caps":326,"nprcue":1411,"nbump":31797,"loplus":2243,"utilde":2290,"Bumpeq":2915,"LeftDownVector":14931,"blacktriangledown":58035,"rsquor":3491,"Ycirc":44034,"DoubleLongLeftArrow":38355,"sqcaps":5238,"Oscr":4180,"GreaterFullEqual":3043,"lessdot":31123,"Cross":45619,"kopf":5812,"Ecy":45762,"Dot":11458,"Zcaron":8370,"rppolint":64243,"subseteqq":6371,"imagline":6579,"nRightarrow":18259,"Efr":47716,"tscr":10740,"Jsercy":12178,"backcong":8131,"ldsh":11491,"tscy":11906,"Rightarrow":24771,"minus":49699,"xfr":49844,"LeftUpVectorBar":43219,"vprop":7971,"Lang":19699,"ecaron":21074,"NotSucceedsSlantEqual":4867,"llarr":12131,"HilbertSpace":32483,"RightUpVector":45203,"gjcy":22226,"RightTeeArrow":15459,"SHcy":25026,"LeftDownVectorBar":39219,"DownRightVectorBar":13683,"Fcy":57762,"Lcedil":26178,"NotEqualTilde":13093,"dtrif":58035,"aacute":2834,"sqsupset":3251,"amacr":58706,"Larr":26899,"urcorn":28323,"Topf":27604,"ycy":59458,"leqq":17603,"Ffr":60036,"yen":59218,"lesg":29270,"Auml":3330,"CloseCurlyDoubleQuote":26339,"dstrok":30290,"rarrsim":53923,"Eta":61490,"longleftarrow":36723,"yfr":61684,"hkswarow":28787,"duhar":18803,"NotTildeTilde":32339,"varrho":20994,"sigmaf":33154,"nsubseteq":53715,"yopf":32772,"zdot":33074,"incare":34291,"CloseCurlyQuote":3491,"Uogon":63154,"ngsim":17331,"sigmav":33154,"luruhar":57363,"lacute":36642,"frasl":63923,"bnot":36595,"rdldhar":58355,"plus":37281,"nesear":23795,"ccups":65363,"amalg":65763,"kgreen":40338,"Egrave":4706,"nVdash":40595,"LongRightArrow":19395,"smile":61363,"FilledVerySmallSquare":27667,"prurel":42051,"DotEqual":1587,"acirc":45122,"Gcy":67746,"RBarr":9683,"VeryThinSpace":275,"Square":44211,"looparrowright":35619,"NotLeftTriangle":10691,"zcy":2610,"OverParenthesis":31939,"Gfr":3716,"ntrianglerighteq":15027,"ndash":4787,"NotLessTilde":58451,"nsupseteqq":30597,"Otilde":5618,"zfr":7572,"NotRightTriangle":12307,"Fscr":48531,"odsold":49395,"bigtriangledown":53379,"simplus":67667,"bopf":49444,"cdot":49778,"LeftArrowRightArrow":14371,"Hat":15553,"Ouml":6418,"zeta":51266,"elsdot":52771,"swarr":17731,"kscr":52916,"pluscir":5139,"NotReverseElement":23347,"varepsilon":25554,"NotCongruent":28099,"prnap":20707,"origof":55059,"CirclePlus":22259,"angrtvb":8083,"triangleright":22099,"boxH":55939,"ltrie":23155,"ltrif":23203,"Hfr":23843,"subplus":11315,"imacr":24146,"zigrarr":11987,"boxV":57043,"nexists":13171,"Therefore":20163,"boxminus":5763,"swnwar":58659,"GreaterTilde":2867,"scsim":1507,"iprod":16723,"Uarrocir":63043,"boxh":57139,"lessgtr":15619,"half":34114,"image":27907,"boxv":59987,"Otimes":60835,"hbar":6227,"LeftFloor":33107,"triangle":51699,"cent":7890,"ssmile":61363,"RightDoubleBracket":8803,"DiacriticalAcute":12658,"nrarr":31987,"Kopf":63092,"erDot":8547,"zwj":33907,"urcrop":63795,"doteqdot":40659,"NotLessGreater":19875,"Icy":36770,"DiacriticalDoubleAcute":22482,"Tscr":65268,"NotLessSlantEqual":30677,"harr":19747,"midast":64369,"ClockwiseContourIntegral":2691,"icirc":39266,"ShortRightArrow":7059,"popf":66148,"UnderParenthesis":41587,"Ifr":27907,"xhArr":35331,"varnothing":11667,"rightleftarrows":30979,"vdash":21187,"yscr":580,"zhcy":2210,"gnap":4483,"gammad":6098,"imath":46450,"roplus":9427,"ncong":19299,"bnequiv":39686,"risingdotseq":8547,"npolint":40803,"gneq":9475,"Int":48995,"forall":14179,"ljcy":13362,"downdownarrows":21107,"succnapprox":5651,"Jcy":49810,"Tstrok":15426,"operp":50403,"fltns":50691,"midcir":18307,"Theta":52738,"Jfr":52852,"bigvee":20659,"Yopf":20932,"Gcedil":21794,"Zdot":21154,"dharl":14931,"alpha":55474,"dharr":22867,"urtri":56307,"NotTildeFullEqual":19299,"precapprox":17251,"cylcty":24979,"exist":4243,"bscr":24676,"Aring":48578,"varr":99,"upsilon":13938,"chcy":25682,"rightthreetimes":56131,"nVDash":27267,"nlsim":58451,"backepsilon":41282,"orslope":51923,"eogon":59634,"DoubleLongRightArrow":25379,"capbrcup":26035,"sqsub":33859,"prap":17251,"SHCHcy":30354,"Rcedil":31250,"gacute":31490,"sqsup":3251,"Kcy":61650,"dzigrarr":29075,"DownLeftVectorBar":18483,"NotSquareSupersetEqual":14707,"bsim":20755,"cedil":15266,"emptyv":11667,"prec":31635,"bigoplus":16259,"eDDot":5411,"Kfr":63316,"langle":32035,"RoundImplies":50451,"cwint":63875,"eqslantless":23955,"Oslash":13554,"yucy":39346,"DZcy":39602,"between":21459,"Bopf":40132,"Omicron":60546,"Cdot":40306,"lceil":66611,"racute":42098,"boxplus":57491,"bsol":41937,"lAtail":42579,"rsquo":3491,"Zeta":42226,"NestedLessLess":20851,"Poincareplane":23843,"prnE":42995,"nsqsube":48915,"rbarr":1267,"middot":7154,"Kscr":43476,"CircleTimes":10147,"sigma":3122,"gopf":44644,"dagger":45155,"vellip":45347,"quaternions":49907,"macr":14226,"Lcy":7538,"prod":46771,"pscr":47140,"Ubreve":48370,"prop":7971,"Lfr":11572,"hoarr":12995,"yuml":14786,"Utilde":50002,"DownLeftVector":16851,"NonBreakingSpace":35426,"NotTilde":56259,"NotLeftTriangleBar":15989,"RightTriangleEqual":3619,"nlArr":15939,"nvHarr":52147,"lhard":16851,"mapstoleft":23443,"curlywedge":40931,"nltri":10691,"NotSubset":22582,"notinE":53637,"iquest":15394,"cirE":53475,"lharu":20307,"rangd":20467,"male":54067,"range":20611,"rbbrk":21411,"Map":22307,"malt":55795,"Gcirc":22514,"curren":15826,"scedil":56498,"raemptyv":46963,"circ":56466,"cire":28051,"SmallCircle":29171,"egsdot":57443,"nwarr":23747,"olarr":11763,"lhblk":26131,"nsqsupe":14707,"profline":48739,"Mcy":26258,"Popf":58307,"agrave":16306,"subsetneq":13459,"leftarrowtail":41827,"Ecaron":60802,"NotSquareSubset":51797,"Mfr":29492,"searr":29795,"vDash":30019,"Yscr":61156,"Lsh":25763,"EqualTilde":5363,"uopf":61908,"mDDot":31443,"bull":16483,"emptyset":11667,"LeftCeiling":66611,"ycirc":32834,"swarhk":28787,"bump":2915,"Aacute":16898,"cross":35459,"percnt":64225,"rmoustache":24179,"supseteqq":29651,"easter":65027,"lbrke":37731,"prsim":37171,"lnap":51747,"rightarrow":7059,"Dstrok":66754,"lfisht":67171,"ngeqslant":25221,"llcorner":1363,"nhpar":41027,"Vbar":67459,"Omacr":42498,"Ncy":42818,"NotVerticalBar":14259,"simdot":1939,"solbar":2643,"bottom":2787,"lneq":2131,"qint":2403,"olcir":43779,"intlarhk":67875,"Lacute":4562,"OpenCurlyDoubleQuote":18899,"YAcy":3538,"nltrie":3779,"Nfr":45396,"lrhard":6531,"SquareUnion":32531,"cwconint":2691,"timesbar":63267,"hksearow":3155,"bigodot":38275,"hardcy":10082,"Bscr":9587,"leftrightharpoons":17651,"triplus":42003,"vert":13969,"upsi":13938,"vltri":24435,"varsupsetneqq":36502,"topfork":43091,"xoplus":16259,"bbrktbrk":12435,"gscr":16211,"lmoust":4003,"gtrarr":19491,"REG":26994,"Ocirc":53554,"ldrushar":16675,"ldquor":21363,"larrfs":21747,"tprime":22147,"LessEqualGreater":3,"Ocy":54882,"hybull":23011,"Not":55747,"uogon":56178,"larrhk":23699,"DoubleRightArrow":24771,"Ofr":57268,"gsim":2867,"daleth":26387,"blank":59011,"lotimes":51171,"Jukcy":59250,"andslope":25475,"lharul":28931,"nvdash":28883,"upharpoonleft":30499,"LeftArrowBar":18035,"larrlp":1459,"rBarr":6483,"gtcc":30883,"Gopf":31732,"DiacriticalGrave":14353,"ropar":62707,"lpar":32673,"vsubnE":33766,"LeftDoubleBracket":6771,"OverBracket":54259,"female":34499,"larrpl":35667,"Element":3875,"cirscir":57219,"thickapprox":12083,"Pscr":36308,"darr":36883,"dash":36979,"race":37301,"lopf":37668,"vsubne":13462,"Pcy":65154,"hamilt":32483,"Assign":39091,"angrtvbd":35379,"Updownarrow":14979,"Yuml":39826,"xdtri":53379,"larrtl":41827,"rangle":28371,"DoubleUpArrow":10499,"Pfr":67508,"uscr":41876,"sdotb":52339,"utdot":68723,"sdote":195,"verbar":13969,"Phi":418,"upuparrows":58403,"congdot":38867,"gnapprox":4483,"spades":44899,"boxDL":3827,"boxDR":5043,"ccedil":22402,"nexist":13171,"rAtail":46403,"NotTildeEqual":1219,"PrecedesTilde":37171,"boxDl":9347,"DifferentialD":18211,"nvrtrie":66342,"boxHD":10035,"boxDr":10451,"curlyeqprec":28275,"lparlt":48835,"straightepsilon":25554,"gtreqless":5187,"boxHU":13267,"lozf":49347,"lbrace":50225,"ntriangleleft":10691,"lbrack":28033,"rang":28371,"raquo":14818,"boxHd":15571,"simgE":15747,"Wcirc":15906,"gtrapprox":8499,"Gammad":52418,"nearrow":4099,"ncedil":53074,"olcross":4435,"softcy":53170,"boxHu":18131,"Uopf":53108,"marker":53875,"simeq":9939,"Qfr":19636,"because":7283,"ddarr":21107,"Psi":21570,"oplus":22259,"smtes":22918,"ldrdhar":10323,"twoheadleftarrow":26899,"NotLessEqual":30451,"rarr":7059,"lrcorner":11219,"simlE":24227,"zopf":56884,"Proportional":7971,"xrArr":25379,"gtrdot":38403,"rarrb":26083,"rarrc":10195,"rarrw":13827,"nwarhk":60883,"topbot":60979,"NotPrecedesSlantEqual":1411,"lthree":42883,"simne":30835,"notindot":60133,"Rcy":32162,"boxUL":32227,"checkmark":10947,"elinters":61315,"boxUR":32931,"boxVH":32979,"boxVL":33635,"ntriangleright":12307,"notniva":23347,"boxVR":34643,"notnivb":24627,"notnivc":24819,"Omega":35970,"Rfr":2995,"topcir":64931,"nearr":4099,"boxUl":37075,"boxUr":37987,"boxVh":38227,"simrarr":27443,"boxVl":38819,"Rho":39058,"gvnE":50134,"boxVr":39555,"uArr":10499,"RightArrowLeftArrow":30979,"Upsi":47202,"Vert":13587,"VerticalLine":13969,"comp":34883,"cong":47779,"GreaterEqual":21923,"utri":51699,"leftrightsquigarrow":42131,"Gscr":68372,"nleftarrow":8707,"NotHumpDownHump":31797,"copf":996,"NotExists":13171,"smeparsl":62419,"rfisht":4947,"theta":44786,"copy":4914,"ratio":46195,"lscr":6964,"rdca":7635,"subrarr":37779,"notinva":15219,"notinvb":38499,"rotimes":38675,"notinvc":38627,"Scy":47106,"nvinfin":39139,"otimesas":5955,"nvsim":47526,"Racute":11138,"uuml":10658,"lowbar":12481,"aring":48706,"acute":12658,"ohbar":49107,"Sfr":49508,"nhArr":49955,"nvDash":15299,"xodot":38275,"Proportion":52243,"Rsh":46627,"Dagger":16435,"preceq":11859,"blk12":51123,"blk14":51459,"boxdL":51507,"filig":51411,"lesssim":11171,"bemptyv":44595,"boxdR":52195,"lsim":11171,"Colon":52243,"rarrap":18723,"period":20561,"LeftTriangleEqual":23155,"blk34":54019,"Longleftarrow":38355,"NotSquareSuperset":3253,"rcub":21025,"succapprox":21603,"gvertneqq":50134,"Tab":55105,"boxdl":55267,"KHcy":21714,"boxhD":55651,"boxdr":55891,"Coproduct":7379,"rmoust":24179,"ltcc":24051,"Darr":24099,"lowast":24867,"Tau":56818,"boxhU":56979,"Lopf":24916,"ReverseUpEquilibrium":18803,"real":2995,"rdquor":26339,"Jcirc":58162,"lcaron":26770,"rarrfs":26803,"boxhd":58259,"odblac":27154,"cemptyv":51219,"circeq":28051,"Tcy":59186,"thicksim":14131,"SquareSubsetEqual":15859,"rarrhk":28691,"lsqb":28033,"boxhu":59939,"rlarr":30979,"Uscr":28580,"rect":28739,"searrow":29795,"qopf":29700,"latail":30787,"nrightarrow":31987,"NotCupCap":15347,"Tfr":61524,"measuredangle":37907,"suplarr":54355,"Scedil":32082,"TildeTilde":12083,"RightAngleBracket":28371,"rharul":34243,"zscr":33572,"seswar":35027,"oline":24003,"rdsh":34451,"rarrlp":35619,"Sub":53827,"Agrave":28002,"Sum":61267,"Sup":2563,"nvlArr":38179,"demptyv":58787,"ominus":30547,"ovbar":65939,"rarrpl":41491,"NestedGreaterGreater":5875,"MinusPlus":36259,"Rang":41443,"sacute":42850,"boxuL":68579,"lrarr":14371,"boxuR":675,"boxvH":947,"nedot":1589,"diams":1891,"Ucy":1762,"boxvL":1843,"frown":2515,"boxvR":3203,"cupbrcap":42627,"lArr":28227,"rarrtl":46019,"LeftTeeVector":34979,"thinsp":46147,"NotSubsetEqual":53715,"eqcolon":22723,"boxul":6003,"RightUpTeeVector":18083,"ltri":46067,"DownArrowBar":64979,"Ufr":6820,"boxur":7187,"boxvh":7491,"boxvl":7843,"Rarr":46723,"gcirc":8850,"rpargt":47427,"boxvr":8883,"DDotrahd":59491,"Zopf":33715,"lesdotor":45715,"searhk":3155,"ddagger":16435,"dwangle":65987,"supsetneq":41075,"alefsym":43395,"doublebarwedge":9155,"Diamond":16963,"equals":881,"cscr":50036,"KJcy":50818,"trade":16387,"leftharpoonup":20307,"infintie":50931,"rbrace":21025,"hearts":54835,"rbrack":47409,"uparrow":5539,"circledast":27315,"Vcy":22546,"Vee":20659,"becaus":7283,"CapitalDifferentialD":46307,"abreve":56786,"Fouriertrf":48531,"tcedil":56946,"cirfnint":55699,"YIcy":57186,"circledcirc":62371,"atilde":30258,"Vfr":25844,"bigstar":2083,"LeftRightVector":23491,"diam":16963,"UnderBracket":60931,"rightleftharpoons":16067,"rtrie":3619,"rtrif":29843,"Copf":59379,"primes":58307,"NotEqual":46675,"lvnE":23062,"omacr":30754,"planck":6227,"FilledSmallSquare":7107,"sfrown":2515,"digamma":6098,"UpArrowDownArrow":26579,"ulcorner":21315,"Lscr":23251,"iecy":63730,"hopf":64004,"rthree":56131,"MediumSpace":55843,"mlcp":64643,"Uuml":31330,"UpDownArrow":99,"natural":7763,"mldr":33027,"permil":66947,"qscr":66884,"rtriltri":59331,"csub":66995,"NotGreaterGreater":5877,"thkap":12083,"veeeq":42179,"csup":68083,"Wfr":42308,"nsimeq":1219,"imped":43442,"subdot":3363,"varsubsetneqq":33766,"awint":45251,"plankv":6227,"ocirc":45554,"NotSquareSubsetEqual":48915,"hookleftarrow":23699,"djcy":9026,"nrarrc":10197,"napE":10581,"RightCeiling":7715,"NotGreaterFullEqual":3045,"lsaquo":12387,"gtreqqless":27027,"Verbar":13587,"angle":12899,"sqsupseteq":6179,"nrarrw":13829,"nang":12902,"laemptyv":9843,"plussim":42451,"uHar":14451,"planckh":43139,"coloneq":39091,"jukcy":50786,"brvbar":16930,"ltimes":17155,"Ccedil":17298,"Qopf":16531,"Emacr":51970,"Kappa":52386,"iiint":16163,"wedbar":20115,"LeftUpDownVector":66099,"ctdot":54163,"rhov":20994,"UnderBrace":14035,"Xfr":54404,"Zscr":21508,"curlyvee":17939,"vopf":22772,"RightFloor":4595,"gtquest":49251,"Precedes":31635,"angrt":57539,"smashp":26291,"timesb":26723,"timesd":26947,"uuarr":58403,"angst":48578,"bumpE":58547,"NotPrecedes":51,"Ncedil":27714,"cacute":28146,"supplus":52291,"lltri":59827,"ncap":28643,"straightphi":3458,"infin":60611,"cups":29558,"supdot":31075,"bumpe":9203,"DiacriticalTilde":12274,"rcaron":31906,"udblac":32306,"sqcups":32582,"Ecirc":62626,"ring":32898,"Ycy":63010,"UnionPlus":29123,"puncsp":35923,"scnsim":36211,"ratail":36931,"Yfr":64788,"nacute":38594,"supseteq":25587,"ThinSpace":46147,"circleddash":22019,"VerticalBar":26483,"zwnj":39507,"nbsp":35426,"phmmat":25715,"nabla":31203,"mcomma":41635,"Cscr":40852,"RuleDelayed":28467,"biguplus":24483,"bNot":42259,"rlhar":16067,"wcirc":1186,"starf":2083,"nearhk":44947,"hscr":45060,"veebar":46355,"yacute":36050,"DownLeftRightVector":59059,"rceil":7715,"circlearrowright":45459,"angzarr":66291,"Zcy":10114,"Ubrcy":10546,"plustwo":68291,"LongLeftRightArrow":25427,"preccurlyeq":20019,"quot":36865,"grave":14353,"equest":50531,"UpArrowBar":53971,"Zfr":14547,"cupcap":51555,"urcorner":28323,"lrhar":17651,"DoubleLeftRightArrow":2035,"LongLeftArrow":36723,"AElig":20898,"ncup":54579,"varpropto":7971,"lHar":55171,"Aogon":22066,"odash":22019,"Breve":9394,"DoubleDownArrow":37027,"Hopf":49907,"Idot":56626,"ShortDownArrow":36883,"swArr":24339,"blacksquare":27667,"omega":25106,"models":58083,"Tilde":14131,"rhard":25299,"LessGreater":15619,"PrecedesEqual":11859,"triangleleft":46067,"Qscr":58948,"rharu":27187,"mopf":59716,"gbreve":60306,"setminus":14499,"LessFullEqual":17603,"ReverseEquilibrium":17651,"nsime":1219,"NegativeMediumSpace":2323,"vscr":62564,"sstarf":56019,"eplus":34835,"NotLess":24531,"Lcaron":64066,"Odblac":64338,"target":64387,"downarrow":36883,"ApplyFunction":28979,"barvee":64595,"ltquest":25987,"nrArr":18259,"VerticalTilde":9987]`; 14 | package __gshared immutable static ubyte[4298] bytes_ = [226,139,154,226,138,128,226,134,149,226,165,164,226,169,166,195,141,226,128,138,226,136,169,239,184,128,206,166,206,191,226,169,189,226,169,139,240,157,147,142,197,164,226,149,152,226,167,132,226,136,179,36,226,170,187,61,226,131,165,226,149,170,240,157,149,148,226,138,137,226,135,155,196,166,197,181,226,137,132,226,164,141,226,128,181,226,140,158,226,139,160,226,134,171,226,137,191,204,184,226,137,144,204,184,226,169,185,226,159,137,208,163,226,136,165,226,149,161,226,153,166,226,169,170,226,137,164,226,135,148,226,152,133,226,170,135,195,166,208,182,226,168,173,197,169,226,128,139,195,171,226,168,140,240,157,146,185,226,140,162,226,139,145,208,183,226,140,191,226,136,178,226,132,149,226,138,165,195,161,226,137,179,226,137,142,208,166,226,132,156,226,137,167,204,184,207,131,226,164,165,226,149,158,226,138,144,204,184,195,132,226,170,189,226,136,171,207,149,226,128,153,208,175,226,136,190,226,138,181,226,139,136,240,157,148,138,226,139,172,226,149,151,226,136,136,209,149,226,137,169,226,142,176,226,139,130,226,134,151,208,137,240,157,146,170,226,136,131,226,166,154,226,138,170,226,132,157,226,166,187,226,170,138,195,185,196,185,226,140,139,240,157,148,169,195,136,226,170,188,226,128,147,102,106,226,139,161,194,169,226,165,189,226,137,189,226,149,148,226,170,165,226,168,162,226,139,155,226,138,147,239,184,128,204,145,226,137,130,226,169,183,226,134,182,195,158,226,134,145,194,171,195,149,226,170,186,206,181,197,138,226,138,159,240,157,149,156,226,137,171,204,184,226,168,182,226,148,152,226,136,184,207,157,226,135,181,226,138,146,226,132,143,226,137,150,226,169,138,226,171,133,195,150,197,129,226,164,143,226,165,173,226,132,144,226,170,179,226,137,175,226,170,164,226,159,166,240,157,148,152,197,131,226,139,159,240,157,147,129,195,170,226,134,146,226,151,188,194,183,226,148,148,226,139,146,226,136,181,226,132,169,226,136,144,240,157,149,141,226,148,188,208,155,240,157,148,183,226,164,183,209,145,226,140,137,226,153,174,194,163,226,148,164,194,162,226,170,142,226,136,157,195,147,209,155,226,138,190,226,137,140,226,136,154,240,157,146,178,226,138,130,196,133,197,189,226,165,190,226,132,152,226,170,134,226,137,147,240,157,148,184,226,171,131,226,134,154,226,168,184,226,159,167,196,157,226,148,156,226,139,141,226,153,163,209,146,226,170,144,226,129,162,226,140,134,226,137,143,204,184,206,179,197,137,226,149,150,203,152,226,168,174,226,170,136,240,157,148,160,226,132,172,226,166,142,226,164,144,240,157,149,164,226,138,191,226,166,180,226,135,137,226,137,131,226,137,128,226,149,166,209,138,208,151,226,138,151,226,164,179,204,184,226,131,155,226,165,167,195,183,226,169,186,226,149,147,226,135,145,208,142,226,169,176,204,184,195,188,226,139,170,240,157,147,137,226,138,134,226,166,145,226,133,136,226,156,147,226,137,185,226,134,165,226,159,188,197,148,226,137,178,226,140,159,226,156,182,226,170,191,226,138,131,226,131,146,194,168,226,134,178,203,135,240,157,148,143,207,135,226,136,133,226,171,151,226,134,186,226,137,168,226,170,175,209,134,226,128,135,226,135,157,226,157,178,226,137,136,226,135,135,208,136,240,157,148,174,203,156,226,139,171,197,154,226,128,185,226,142,182,95,240,157,149,128,226,166,144,226,139,168,194,180,226,130,172,195,151,209,143,226,128,132,226,138,129,226,136,160,226,131,146,226,135,191,226,128,133,226,137,130,204,184,226,136,132,226,165,174,226,149,169,226,150,191,209,153,206,148,208,139,226,138,138,239,184,128,195,152,226,128,150,226,137,187,226,165,151,208,131,240,157,146,165,226,134,157,204,184,195,130,207,133,124,226,136,166,226,143,159,226,165,159,226,136,188,226,136,128,194,175,226,136,164,226,140,133,96,226,135,134,203,153,226,165,163,226,136,150,226,132,168,207,136,226,171,135,197,128,226,139,163,195,154,195,191,194,187,195,187,226,138,132,226,135,131,226,135,149,226,139,173,240,157,149,151,206,158,226,151,139,226,136,137,194,184,226,138,173,226,137,173,194,191,197,166,226,134,166,226,151,172,94,226,149,164,226,137,182,196,161,226,170,139,226,170,160,208,178,194,164,226,138,145,197,180,226,135,141,226,167,143,204,184,226,135,140,226,136,168,226,136,173,226,132,138,226,168,129,195,160,226,138,141,226,132,162,226,128,161,226,128,162,226,132,154,226,169,184,226,132,173,226,165,139,226,168,188,226,166,167,195,144,226,134,189,195,129,194,166,226,139,132,226,128,149,240,157,148,165,206,163,226,139,137,226,138,133,226,170,183,195,135,226,137,181,226,132,155,226,164,150,226,170,129,226,168,187,194,161,226,137,166,226,135,139,208,167,226,134,153,38,226,165,148,226,167,144,226,169,175,226,139,142,226,167,141,226,135,164,226,165,156,226,149,167,196,141,226,133,134,226,135,143,226,171,176,226,139,166,194,181,226,136,139,226,165,150,226,166,168,226,137,156,226,164,159,226,166,169,226,165,181,206,167,226,165,175,226,129,151,226,128,156,226,165,158,226,170,175,204,184,226,166,170,206,153,240,157,149,159,195,157,226,128,186,226,137,135,226,166,171,226,159,182,226,151,175,226,165,184,226,166,172,226,129,160,240,157,148,148,226,159,170,226,134,148,226,166,173,196,143,226,137,184,226,138,153,226,139,148,226,137,188,226,166,174,226,169,159,226,136,180,226,168,130,226,166,175,226,134,188,226,165,149,240,157,148,179,226,166,146,226,132,180,46,197,185,226,166,165,226,139,129,226,170,185,226,136,189,226,165,157,226,137,170,195,134,240,157,149,144,207,177,125,208,188,196,155,226,135,138,197,187,226,138,162,226,170,176,204,184,226,140,156,226,128,158,226,157,179,226,137,172,240,157,146,181,206,168,226,170,184,240,157,148,187,208,165,226,164,157,196,162,226,171,168,226,169,150,226,137,165,226,168,141,226,138,157,196,132,226,150,185,226,128,180,196,150,209,147,226,138,149,226,164,133,226,151,138,195,167,226,153,173,203,157,196,156,208,146,226,138,130,226,131,146,226,132,176,226,137,149,240,157,149,167,206,147,226,135,130,226,170,172,239,184,128,226,129,131,226,137,168,239,184,128,226,138,180,226,151,130,226,132,146,226,166,191,226,136,140,226,166,150,226,134,164,226,165,142,226,139,138,240,157,147,140,226,167,182,226,134,169,226,134,150,226,164,168,226,132,140,240,157,149,146,226,170,149,226,128,190,226,170,166,226,134,161,196,171,226,142,177,226,170,159,240,157,148,170,226,135,153,226,171,140,226,138,178,226,168,132,226,137,174,226,136,174,226,139,190,240,157,146,183,208,180,226,135,146,226,139,189,226,136,151,240,157,149,131,226,140,173,208,168,226,137,177,207,137,226,128,142,196,165,226,169,190,204,184,226,135,129,194,176,226,159,185,226,159,183,226,169,152,196,182,207,181,226,138,135,226,139,167,209,135,226,132,179,226,134,176,197,175,240,157,148,153,226,165,138,197,157,226,169,187,226,169,137,226,135,165,226,150,132,196,187,226,171,139,208,156,226,168,179,226,128,157,226,132,184,226,171,166,226,136,163,226,132,167,226,135,133,209,128,240,157,149,154,226,138,160,196,190,226,164,158,226,131,156,226,134,158,226,168,176,194,174,226,170,140,194,185,226,147,136,197,145,226,135,128,194,178,226,138,175,226,138,155,194,179,226,129,143,226,165,178,240,157,146,191,226,171,130,240,157,149,139,226,150,170,197,133,240,157,148,161,226,136,187,226,171,153,226,132,145,226,166,143,195,128,91,226,137,151,226,137,162,196,135,226,128,177,226,135,144,226,139,158,226,140,157,226,159,169,226,129,129,226,167,180,209,133,197,136,240,157,146,176,226,169,131,226,134,170,226,150,173,226,164,166,226,168,134,226,138,172,226,165,170,226,129,161,226,137,139,226,159,191,226,138,142,226,136,152,226,128,165,226,139,154,239,184,128,226,134,183,226,136,130,207,176,240,157,148,144,226,136,170,239,184,128,226,171,134,240,157,149,162,195,169,226,134,152,226,150,184,208,148,207,145,240,157,148,175,226,138,168,226,166,141,226,128,178,226,135,150,226,169,179,195,163,196,145,208,184,208,169,240,157,147,135,226,137,176,226,134,191,226,138,150,226,171,134,204,184,226,169,189,204,184,197,141,226,164,153,226,137,134,226,170,167,226,137,141,226,135,132,226,134,181,226,170,190,226,139,150,196,137,226,136,135,197,150,226,153,170,195,156,209,136,226,137,153,226,136,186,199,181,208,174,226,171,149,206,185,226,137,186,226,170,132,240,157,148,190,226,137,142,204,184,208,172,197,153,226,143,156,226,134,155,226,159,168,197,158,226,135,154,208,160,206,146,226,149,157,206,149,197,177,226,137,137,207,129,196,130,195,143,226,132,139,226,138,148,226,138,148,239,184,128,40,197,161,226,136,148,240,157,149,170,197,183,197,162,203,154,226,149,154,226,149,172,226,128,166,197,188,226,140,138,207,130,195,173,226,164,189,239,172,131,240,157,148,135,226,129,163,226,137,138,226,137,183,226,171,147,240,157,147,143,226,149,163,197,165,226,132,164,226,171,139,239,184,128,226,138,143,226,128,141,240,157,149,149,240,157,148,166,196,167,194,189,226,170,173,239,184,128,226,165,172,226,132,133,226,133,147,196,151,194,188,226,134,179,226,153,128,226,165,146,226,133,149,226,149,160,226,133,153,226,170,182,226,133,135,226,169,177,226,136,129,226,169,135,226,165,154,226,164,169,196,170,226,132,175,226,139,185,226,133,155,195,131,226,170,154,226,159,186,226,166,157,194,160,226,156,151,240,157,149,134,226,168,150,226,134,172,226,164,185,206,155,195,159,226,139,128,226,135,152,226,170,153,226,128,136,206,169,226,133,148,195,189,226,128,131,226,170,180,196,164,226,139,169,226,136,147,240,157,146,171,195,142,226,143,158,226,133,150,226,171,140,239,184,128,226,140,144,196,186,226,132,150,226,159,181,208,152,240,157,148,180,34,226,134,147,226,164,154,226,128,144,226,135,147,226,149,156,226,170,128,226,137,190,197,174,208,189,43,226,136,189,204,177,197,156,226,139,131,226,138,152,226,150,179,226,137,141,226,131,146,10,240,157,149,157,226,166,139,226,165,185,226,167,133,195,176,226,136,161,194,190,226,149,153,226,133,151,226,139,155,239,184,128,226,164,130,226,149,171,226,168,128,197,130,226,159,184,226,139,151,226,138,179,226,139,183,226,133,156,197,132,226,139,182,226,168,181,196,158,240,157,147,130,226,149,162,226,169,173,204,184,226,128,143,240,157,149,142,206,161,226,137,148,226,167,158,197,171,226,165,153,195,174,226,165,168,209,142,226,139,179,226,138,164,195,179,226,128,140,226,149,159,208,143,226,139,181,226,137,161,226,131,165,226,133,152,197,184,206,165,240,157,146,179,197,190,226,151,184,226,139,147,226,170,162,240,157,148,185,226,134,144,226,136,176,62,196,138,196,184,226,168,170,226,167,144,204,184,226,171,136,226,169,181,226,138,174,39,226,137,145,240,157,148,171,209,150,226,168,148,240,157,146,158,33,226,139,143,226,153,175,226,171,178,226,138,139,239,184,128,240,157,149,165,226,164,188,207,182,226,136,167,226,133,154,209,141,226,159,171,226,165,133,226,133,157,226,143,157,226,168,169,226,169,191,226,139,180,226,138,171,226,134,162,240,157,147,138,92,226,128,179,226,168,185,226,138,176,197,149,226,134,173,226,137,154,206,150,226,171,173,240,157,148,154,226,135,151,196,168,226,168,166,197,140,226,169,180,226,164,155,226,169,136,195,164,226,171,167,209,152,196,136,208,157,197,155,226,139,139,240,157,149,129,226,170,181,226,169,128,226,171,154,226,132,142,209,129,226,165,152,226,137,166,204,184,226,165,160,226,132,181,198,181,240,157,146,166,226,166,183,195,140,194,167,226,136,190,204,179,226,133,158,226,166,190,239,172,128,226,171,133,204,184,196,147,239,172,132,197,182,240,157,148,162,226,170,130,195,186,226,150,161,58,226,165,147,206,186,226,137,165,226,131,146,226,165,155,226,171,175,226,166,140,226,166,176,240,157,149,152,226,139,188,197,167,206,184,226,171,179,206,178,226,153,160,226,164,164,195,175,196,175,240,157,146,189,195,162,226,128,160,226,134,190,226,168,145,226,170,150,226,139,174,240,157,148,145,226,134,187,226,132,182,195,180,209,159,226,168,175,226,138,163,226,170,131,208,173,240,157,148,176,62,226,131,146,195,145,240,157,146,174,226,134,163,226,151,131,208,185,226,128,137,226,136,182,226,134,174,60,226,133,133,226,138,187,226,164,156,196,177,196,178,240,157,149,160,226,136,191,226,134,177,226,137,160,226,134,160,226,136,143,226,157,152,226,165,161,226,138,184,226,166,179,226,171,169,226,167,156,208,161,240,157,147,133,207,146,197,186,206,188,195,146,196,179,226,134,167,93,226,166,148,226,171,152,226,136,188,226,131,146,197,170,64,226,169,149,240,157,148,136,226,137,133,240,157,148,188,194,172,226,139,135,226,168,165,206,189,226,171,164,194,173,240,157,148,167,226,140,141,226,136,130,204,184,226,167,165,197,172,226,150,136,226,169,141,208,176,226,132,177,195,133,240,157,149,168,208,134,195,165,226,140,146,226,150,171,226,166,147,207,128,226,139,162,195,172,226,136,172,240,157,147,141,226,166,181,226,137,164,226,131,146,226,169,188,226,170,168,226,167,171,226,166,188,240,157,149,147,240,157,148,150,194,177,226,128,130,226,132,147,226,136,146,196,181,196,139,208,153,240,157,148,181,226,132,141,226,135,142,197,168,240,157,146,184,208,190,226,137,169,239,184,128,123,226,171,132,240,157,149,132,226,159,136,226,166,185,226,165,176,196,183,226,137,159,226,138,169,60,226,131,146,226,150,177,226,168,147,209,148,208,140,240,157,146,169,41,226,167,157,195,153,226,151,187,240,157,148,158,226,150,146,226,168,180,226,166,178,206,182,226,167,163,197,139,196,188,239,172,129,226,150,145,226,149,149,226,169,134,226,139,187,226,128,152,226,150,181,226,170,137,226,138,143,204,184,226,143,162,226,169,151,196,146,226,167,137,209,137,240,157,149,155,226,164,132,226,149,146,226,136,183,226,171,128,226,138,161,206,154,207,156,226,168,186,226,165,165,207,132,226,166,156,44,226,169,133,226,136,162,206,152,226,170,151,195,190,240,157,148,141,240,157,147,128,226,171,148,226,132,151,197,134,240,157,149,140,209,140,208,144,240,157,148,172,196,174,226,132,158,226,150,189,226,168,149,226,167,131,209,132,195,148,226,170,170,226,139,185,204,184,226,138,136,240,157,146,177,226,139,144,226,150,174,226,165,180,226,164,146,226,150,147,226,153,130,226,140,140,226,139,175,226,171,150,226,142,180,226,128,154,226,165,187,240,157,148,155,226,167,129,240,157,146,156,226,169,130,226,139,149,226,150,180,240,157,149,163,226,170,169,226,153,165,208,158,226,134,159,226,165,177,226,164,131,226,138,182,9,226,139,133,226,165,162,226,140,149,226,148,144,195,177,47,226,171,145,195,178,209,130,206,177,240,157,147,136,226,137,170,204,184,226,149,165,226,168,144,226,171,172,226,156,160,226,129,159,226,148,140,226,149,144,197,151,226,139,134,240,157,148,132,226,139,140,197,179,226,170,158,226,137,129,226,151,185,240,157,148,163,226,171,129,203,134,197,159,226,164,142,226,170,157,196,176,209,156,226,169,144,226,165,143,196,131,206,164,200,183,240,157,149,171,197,163,226,149,168,35,226,149,145,226,159,172,226,148,128,208,135,226,167,130,240,157,148,146,196,191,226,165,166,197,146,226,170,152,226,138,158,226,136,159,226,171,189,226,131,165,206,183,226,166,133,208,164,240,157,149,150,240,157,148,177,208,170,226,169,178,208,186,226,150,190,226,138,167,206,190,196,180,240,157,146,187,226,148,172,226,132,153,226,165,169,226,135,136,226,137,180,226,164,182,226,170,174,197,147,206,187,226,164,170,196,129,226,138,186,226,166,177,195,155,226,169,156,208,138,240,157,146,172,226,144,163,226,165,144,226,170,161,204,184,208,162,194,165,208,132,239,172,130,226,167,142,226,132,130,196,140,209,139,226,164,145,226,169,132,226,140,143,196,153,226,167,128,240,157,149,158,226,139,153,226,151,186,194,182,208,149,226,148,180,226,148,130,240,157,148,137,196,142,226,139,181,204,184,240,157,148,168,195,182,196,159,240,157,147,131,226,139,178,226,137,146,226,169,154,206,159,195,184,226,136,158,240,157,149,143,208,177,226,138,185,196,154,226,168,183,226,164,163,226,142,181,226,140,182,226,164,167,203,155,226,170,133,240,157,146,180,226,138,183,226,136,145,226,143,167,226,140,163,226,139,153,204,184,206,151,240,157,148,151,195,168,195,139,208,154,240,157,148,182,240,157,146,159,226,140,174,226,166,164,240,157,149,166,226,152,142,209,151,226,159,173,226,168,163,226,135,189,208,191,226,138,180,226,131,146,226,128,148,226,138,154,226,167,164,226,137,171,226,131,146,240,157,147,139,195,138,226,169,147,226,166,134,206,145,240,157,148,159,59,196,169,226,165,191,240,157,146,182,208,171,226,165,137,240,157,149,130,197,178,209,154,226,139,177,226,168,177,240,157,148,142,226,169,157,226,166,149,226,152,134,195,181,226,136,175,195,137,208,145,240,157,148,173,208,181,194,170,226,140,142,194,186,226,136,177,226,129,132,208,179,240,157,149,153,196,189,226,164,184,198,146,226,164,140,37,226,168,146,226,165,179,197,144,42,226,140,150,226,139,152,240,157,146,190,226,164,181,226,138,189,226,171,155,240,157,149,138,208,150,240,157,148,156,207,134,226,150,128,226,171,177,226,164,147,226,169,174,226,169,155,196,134,208,159,226,170,147,197,135,240,157,146,175,196,128,226,169,140,209,131,226,129,159,226,128,138,226,165,136,208,133,63,226,137,139,204,184,226,170,171,226,168,191,226,139,186,226,139,152,204,184,226,140,189,226,166,166,240,157,148,133,226,165,145,240,157,149,161,226,135,190,206,156,226,141,188,226,138,181,226,131,146,209,158,240,157,148,164,196,152,226,170,148,226,140,136,226,140,147,226,165,171,196,144,226,137,170,226,131,146,240,157,147,134,226,128,176,226,171,143,226,171,174,207,150,226,165,182,226,165,188,226,164,160,226,170,162,204,184,206,157,208,129,226,170,145,226,171,171,240,157,148,147,206,180,240,157,148,189,226,168,164,197,152,208,147,240,157,148,178,196,160,226,168,151,226,170,141,197,176,197,173,226,169,148,226,171,144,226,170,143,226,132,183,208,187,206,160,226,168,167,208,130,240,157,146,162,226,170,146,240,157,149,169,197,160,226,149,155,226,171,146,226,166,182,226,139,176]; 15 | package __gshared immutable static uint[const(char)[]] index_; 16 | 17 | 18 | package auto codeOffset(uint indexValue) { 19 | return indexValue >> 4; 20 | } 21 | 22 | package auto codeLength(uint indexValue) { 23 | return indexValue & 15; 24 | } 25 | 26 | auto getNamedEntityUTF8(const(char)[] name) { 27 | if (name.length) { 28 | if (__ctfe) { 29 | immutable index = mixin(EntityIndexDef); 30 | if (auto pindex = name in index) { 31 | auto offset = codeOffset(*pindex); 32 | auto length = codeLength(*pindex); 33 | return cast(const(char)[])bytes_[offset..offset + length]; 34 | } 35 | } else { 36 | if (auto pindex = name in index_) { 37 | auto offset = codeOffset(*pindex); 38 | auto length = codeLength(*pindex); 39 | return cast(const(char)[])bytes_[offset..offset + length]; 40 | } 41 | } 42 | } 43 | return null; 44 | } 45 | 46 | auto getEntityUTF8(const(char)[] name) { 47 | import std.conv; 48 | 49 | if (name.length > 1) { 50 | if (name[0] == '#') { 51 | int code = 0; 52 | if ((name[1] == 'x') || (name[1] == 'X')) { 53 | name = name[2..min(2+4, $)]; 54 | code = parse!int(name, 16); 55 | } else { 56 | name = name[2..min(1+4, $)]; 57 | code = parse!int(name); 58 | } 59 | 60 | return decodeCodePoint(code); 61 | } 62 | } 63 | return getNamedEntityUTF8(name); 64 | } 65 | 66 | package auto decodeCodePoint(int code) { 67 | import std.range; 68 | 69 | static char[4] buf; 70 | dchar[1] input = [code]; 71 | return buf[0 .. $ - input[ ].byChar().copy(buf[ ].byChar()).length]; 72 | } 73 | 74 | shared static this() { 75 | index_ = mixin(EntityIndexDef); 76 | } -------------------------------------------------------------------------------- /src/html/dom.d: -------------------------------------------------------------------------------- 1 | module html.dom; 2 | 3 | 4 | import std.algorithm; 5 | import std.array; 6 | import std.ascii; 7 | import std.conv; 8 | import std.experimental.allocator; 9 | import std.range; 10 | import std.string; 11 | import std.typecons; 12 | 13 | import html.parser; 14 | import html.utils; 15 | 16 | 17 | alias HTMLString = const(char)[]; 18 | static if(__VERSION__ >= 2079){ 19 | alias IAllocator = RCIAllocator; 20 | } 21 | 22 | enum DOMCreateOptions { 23 | None = 0, 24 | DecodeEntities = 1 << 0, 25 | 26 | ValidateClosed = 1 << 10, 27 | ValidateSelfClosing = 1 << 11, 28 | ValidateDuplicateAttr = 1 << 12, 29 | ValidateBasic = ValidateClosed | ValidateSelfClosing | ValidateDuplicateAttr, 30 | ValidateAll = ValidateBasic, 31 | 32 | Default = DecodeEntities, 33 | } 34 | 35 | 36 | enum ValidationError : size_t { 37 | None, 38 | MismatchingClose = 1, 39 | MissingClose, 40 | StrayClose, 41 | SelfClosingNonVoidElement, 42 | DuplicateAttr, 43 | } 44 | 45 | 46 | alias ValidationErrorCallable = void delegate(ValidationError, HTMLString, HTMLString); 47 | 48 | 49 | alias onlyElements = a => a.isElementNode; 50 | 51 | 52 | private struct ChildrenForward(NodeType, alias Condition = null) { 53 | this(NodeType first) { 54 | curr_ = cast(Node)first; 55 | static if (!is(typeof(Condition) == typeof(null))) { 56 | while (curr_ && !Condition(curr_)) 57 | curr_ = curr_.next_; 58 | } 59 | } 60 | 61 | bool empty() const { 62 | return (curr_ is null); 63 | } 64 | 65 | NodeType front() const { 66 | return cast(NodeType)curr_; 67 | } 68 | 69 | void popFront() { 70 | curr_ = curr_.next_; 71 | 72 | static if (!is(typeof(Condition) == typeof(null))) { 73 | while (curr_) { 74 | if (Condition(curr_)) 75 | break; 76 | curr_ = curr_.next_; 77 | } 78 | } 79 | } 80 | 81 | private Node curr_; 82 | } 83 | 84 | 85 | private struct AncestorsForward(NodeType, alias Condition = null) { 86 | this(NodeType first) { 87 | curr_ = cast(Node)first; 88 | static if (!is(typeof(Condition) == typeof(null))) { 89 | while (curr_ && !Condition(curr_)) 90 | curr_ = curr_.parent_; 91 | } 92 | } 93 | 94 | bool empty() const { 95 | return (curr_ is null); 96 | } 97 | 98 | NodeType front() const { 99 | return cast(NodeType)curr_; 100 | } 101 | 102 | void popFront() { 103 | curr_ = curr_.parent_; 104 | 105 | static if (!is(typeof(Condition) == typeof(null))) { 106 | while (curr_) { 107 | if (Condition(curr_)) 108 | break; 109 | curr_ = curr_.parent_; 110 | } 111 | } 112 | } 113 | 114 | private Node curr_; 115 | } 116 | 117 | 118 | private struct DescendantsForward(NodeType, alias Condition = null) { 119 | this(NodeType top) { 120 | if (top is null) 121 | return; 122 | curr_ = cast(Node)top.firstChild_; // top itself is excluded 123 | top_ = cast(Node)top; 124 | static if (!is(typeof(Condition) == typeof(null))) { 125 | if (!Condition(curr_)) 126 | popFront; 127 | } 128 | } 129 | 130 | bool empty() const { 131 | return (curr_ is null); 132 | } 133 | 134 | NodeType front() const { 135 | return cast(NodeType)curr_; 136 | } 137 | 138 | void popFront() { 139 | while (curr_) { 140 | if (curr_.firstChild_) { 141 | curr_ = curr_.firstChild_; 142 | } else if (curr_ !is top_) { 143 | auto next = curr_.next_; 144 | if (!next) { 145 | Node parent = curr_.parent_; 146 | while (parent) { 147 | if (parent !is top_) { 148 | if (parent.next_) { 149 | next = parent.next_; 150 | break; 151 | } 152 | parent = parent.parent_; 153 | } else { 154 | next = null; 155 | break; 156 | } 157 | } 158 | } 159 | 160 | curr_ = next; 161 | if (!curr_) 162 | break; 163 | } else { 164 | curr_ = null; 165 | break; 166 | } 167 | 168 | static if (is(typeof(Condition) == typeof(null))) { 169 | break; 170 | } else { 171 | if (Condition(curr_)) 172 | break; 173 | } 174 | } 175 | } 176 | 177 | private Node curr_; 178 | private Node top_; 179 | } 180 | 181 | unittest { 182 | const doc = createDocument(`
`); 183 | assert(DescendantsForward!(const(Node))(doc.root).count() == 8); 184 | auto fs = DescendantsForward!(const(Node), x => x.attr("id") == "f")(doc.root); 185 | assert(fs.count() == 1); 186 | assert(fs.front().attr("id") == "f"); 187 | auto hs = DescendantsForward!(const(Node), x => x.attr("id") == "h")(fs.front()); 188 | assert(hs.count() == 1); 189 | assert(hs.front().attr("id") == "h"); 190 | auto divs = DescendantsForward!(const(Node))(fs.front()); 191 | assert(divs.count() == 1); 192 | } 193 | 194 | unittest { 195 | // multiple top-level nodes 196 | const doc = createDocument(`
`); 197 | assert(DescendantsForward!(const Node)(doc.root).count() == 3); 198 | assert(doc.nodes.count() == 3); 199 | } 200 | 201 | unittest { 202 | const doc = createDocument(``); 203 | assert(DescendantsForward!(const Node)(doc.root).empty); 204 | assert(doc.nodes.empty); 205 | } 206 | 207 | private struct QuerySelectorMatcher(NodeType, Nodes) if (isInputRange!Nodes) { 208 | this(Selector selector, Nodes nodes) { 209 | selector_ = selector; 210 | nodes_ = nodes; 211 | 212 | while (!nodes_.empty()) { 213 | auto node = nodes_.front(); 214 | if (node.isElementNode && selector_.matches(node)) 215 | break; 216 | 217 | nodes_.popFront(); 218 | } 219 | } 220 | 221 | bool empty() const { 222 | return nodes_.empty(); 223 | } 224 | 225 | NodeType front() const { 226 | return cast(NodeType)nodes_.front(); 227 | } 228 | 229 | void popFront() { 230 | nodes_.popFront(); 231 | while (!nodes_.empty()) { 232 | auto node = nodes_.front(); 233 | if (node.isElementNode && selector_.matches(node)) 234 | break; 235 | 236 | nodes_.popFront(); 237 | } 238 | } 239 | 240 | private Nodes nodes_; 241 | private Selector selector_; 242 | } 243 | 244 | 245 | enum NodeTypes : ubyte { 246 | Text = 0, 247 | Element, 248 | Comment, 249 | CDATA, 250 | Declaration, 251 | ProcessingInstruction, 252 | } 253 | 254 | 255 | class Node { 256 | @disable this(); 257 | 258 | private this(HTMLString tag, Document document, size_t flags) { 259 | flags_ = flags; 260 | tag_ = tag; 261 | 262 | parent_ = null; 263 | firstChild_ = null; 264 | lastChild_ = null; 265 | 266 | prev_ = null; 267 | next_ = null; 268 | 269 | document_ = document; 270 | } 271 | 272 | auto opIndex(HTMLString name) const { 273 | return attr(name); 274 | } 275 | 276 | void opIndexAssign(HTMLString value, HTMLString name) { 277 | attr(name, value); 278 | } 279 | 280 | void opIndexAssign(T)(T value, HTMLString name) { 281 | attr(name, value.to!string); 282 | } 283 | 284 | @property auto id() const { 285 | return attr("id"); 286 | } 287 | 288 | @property void id(T)(T value) { 289 | attr("id", value.to!string); 290 | } 291 | 292 | @property auto type() const { 293 | return (flags_ & TypeMask) >> TypeShift; 294 | } 295 | 296 | @property auto tag() const { 297 | return isElementNode ? tag_ : null; 298 | } 299 | 300 | @property auto tagHash() const { 301 | return isElementNode ? tagHash_ : 0; 302 | } 303 | 304 | @property auto comment() const { 305 | return isCommentNode ? tag_ : null; 306 | } 307 | 308 | @property auto cdata() const { 309 | return isCDATANode ? tag_ : null; 310 | } 311 | 312 | @property auto declaration() const { 313 | return isDeclarationNode ? tag_ : null; 314 | } 315 | 316 | @property auto processingInstruction() const { 317 | return isProcessingInstructionNode ? tag_ : null; 318 | } 319 | 320 | void text(Appender)(ref Appender app) const { 321 | if (isTextNode) { 322 | app.put(tag_); 323 | } else { 324 | Node child = cast(Node)firstChild_; 325 | while (child) { 326 | child.text(app); 327 | child = child.next_; 328 | } 329 | } 330 | } 331 | 332 | @property auto text() const { 333 | if (isTextNode) { 334 | return tag_; 335 | } else { 336 | Appender!HTMLString app; 337 | text(app); 338 | return app.data; 339 | } 340 | } 341 | 342 | @property void text(HTMLString text) { 343 | if (isTextNode) { 344 | tag_ = text; 345 | } else { 346 | destroyChildren(); 347 | appendText(text); 348 | } 349 | } 350 | 351 | @property void attr(HTMLString name, HTMLString value) { 352 | assert(isElementNode, "cannot set attributes of non-element nodes"); 353 | 354 | attrs_[name] = value; 355 | 356 | if (name == "id") { 357 | if (!value.empty()) { 358 | flags_ |= Node.Flags.HasID; 359 | } else { 360 | flags_ &= ~Node.Flags.HasID; 361 | } 362 | } 363 | } 364 | 365 | @property HTMLString attr(HTMLString name) const { 366 | if (auto pattr = name in attrs_) 367 | return *pattr; 368 | return null; 369 | } 370 | 371 | bool hasAttr(HTMLString name) const { 372 | return (name in attrs_) != null; 373 | } 374 | 375 | void removeAttr(HTMLString name) { 376 | attrs_.remove(name); 377 | } 378 | 379 | @property void html(size_t Options = DOMCreateOptions.Default)(HTMLString html) { 380 | assert(isElementNode, "cannot add html to non-element nodes"); 381 | 382 | enum parserOptions = ((Options & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); 383 | 384 | destroyChildren(); 385 | 386 | auto builder = DOMBuilder!(Document, Options)(document_, this); 387 | parseHTML!(typeof(builder), parserOptions)(html, builder); 388 | } 389 | 390 | @property auto html() const { 391 | Appender!HTMLString app; 392 | innerHTML(app); 393 | return app.data; 394 | } 395 | 396 | @property auto compactHTML() const { 397 | Appender!HTMLString app; 398 | compactInnerHTML(app); 399 | return app.data; 400 | } 401 | 402 | @property auto outerHTML() const { 403 | Appender!HTMLString app; 404 | outerHTML(app); 405 | return app.data; 406 | } 407 | 408 | @property auto compactOuterHTML() const { 409 | Appender!HTMLString app; 410 | compactOuterHTML(app); 411 | return app.data; 412 | } 413 | 414 | void prependChild(Node node) { 415 | assert(document_ == node.document_); 416 | assert(isElementNode, "cannot prepend to non-element nodes"); 417 | 418 | if (node.parent_) 419 | node.detach(); 420 | node.parent_ = this; 421 | if (firstChild_) { 422 | assert(!firstChild_.prev_); 423 | firstChild_.prev_ = node; 424 | node.next_ = firstChild_; 425 | firstChild_ = node; 426 | } else { 427 | assert(!lastChild_); 428 | firstChild_ = node; 429 | lastChild_ = node; 430 | } 431 | } 432 | 433 | void appendChild(Node node) { 434 | assert(document_ == node.document_); 435 | assert(isElementNode, "cannot append to non-element nodes"); 436 | 437 | if (node.parent_) 438 | node.detach(); 439 | node.parent_ = this; 440 | if (lastChild_) { 441 | assert(!lastChild_.next_); 442 | lastChild_.next_ = node; 443 | node.prev_ = lastChild_; 444 | lastChild_ = node; 445 | } else { 446 | assert(!firstChild_); 447 | firstChild_ = node; 448 | lastChild_ = node; 449 | } 450 | } 451 | 452 | void removeChild(Node node) { 453 | assert(node.parent_ is this); 454 | node.detach(); 455 | } 456 | 457 | void destroyChildren() { 458 | auto child = firstChild_; 459 | while (child) { 460 | auto next = child.next_; 461 | child.destroy(); 462 | child = next; 463 | } 464 | 465 | firstChild_ = null; 466 | lastChild_ = null; 467 | } 468 | 469 | void prependText(HTMLString text) { 470 | auto node = document_.createTextNode(text); 471 | if (firstChild_) { 472 | assert(firstChild_.prev_ is null); 473 | firstChild_.prev_ = node; 474 | node.next_ = firstChild_; 475 | firstChild_ = node; 476 | } else { 477 | assert(lastChild_ is null); 478 | firstChild_ = node; 479 | lastChild_ = node; 480 | } 481 | node.parent_ = this; 482 | } 483 | 484 | void appendText(HTMLString text) { 485 | auto node = document_.createTextNode(text); 486 | if (lastChild_) { 487 | assert(lastChild_.next_ is null); 488 | lastChild_.next_ = node; 489 | node.prev_ = lastChild_; 490 | lastChild_ = node; 491 | } else { 492 | assert(firstChild_ is null); 493 | firstChild_ = node; 494 | lastChild_ = node; 495 | } 496 | node.parent_ = this; 497 | } 498 | 499 | void insertBefore(Node node) { 500 | assert(document_ is node.document_); 501 | 502 | parent_ = node.parent_; 503 | prev_ = node.prev_; 504 | next_ = node; 505 | node.prev_ = this; 506 | 507 | if (prev_) 508 | prev_.next_ = this; 509 | else if (parent_) 510 | parent_.firstChild_ = this; 511 | } 512 | 513 | void insertAfter(Node node) { 514 | assert(document_ is node.document_); 515 | 516 | parent_ = node.parent_; 517 | prev_ = node; 518 | next_ = node.next_; 519 | node.next_ = this; 520 | 521 | if (next_) 522 | next_.prev_ = this; 523 | else if (parent_) 524 | parent_.lastChild_ = this; 525 | } 526 | 527 | void detach() { 528 | if (parent_) { 529 | if (parent_.firstChild_ is this) { 530 | parent_.firstChild_ = next_; 531 | if (next_) { 532 | next_.prev_ = null; 533 | next_ = null; 534 | } else { 535 | parent_.lastChild_ = null; 536 | } 537 | 538 | assert(prev_ is null); 539 | } else if (parent_.lastChild_ is this) { 540 | parent_.lastChild_ = prev_; 541 | assert(prev_); 542 | assert(next_ is null); 543 | prev_.next_ = null; 544 | prev_ = null; 545 | } else { 546 | assert(prev_); 547 | 548 | prev_.next_ = next_; 549 | if (next_) { 550 | next_.prev_ = prev_; 551 | next_ = null; 552 | } 553 | prev_ = null; 554 | } 555 | parent_ = null; 556 | } 557 | } 558 | 559 | package void detachFast() { 560 | if (parent_) { 561 | if (parent_.firstChild_ is this) { 562 | parent_.firstChild_ = next_; 563 | if (next_) { 564 | next_.prev_ = null; 565 | } else { 566 | parent_.lastChild_ = null; 567 | } 568 | 569 | assert(prev_ is null); 570 | } else if (parent_.lastChild_ is this) { 571 | parent_.lastChild_ = prev_; 572 | assert(prev_); 573 | assert(next_ is null); 574 | prev_.next_ = null; 575 | } else { 576 | assert(prev_); 577 | 578 | prev_.next_ = next_; 579 | if (next_) { 580 | next_.prev_ = prev_; 581 | } 582 | } 583 | } 584 | } 585 | 586 | void destroy() { 587 | detachFast(); 588 | destroyChildren(); 589 | document_.destroyNode(this); 590 | } 591 | 592 | void innerHTML(Appender)(ref Appender app) const { 593 | auto child = cast(Node)firstChild_; 594 | while (child) { 595 | child.outerHTML(app); 596 | child = child.next_; 597 | } 598 | } 599 | 600 | void compactInnerHTML(Appender)(ref Appender app) const { 601 | auto child = cast(Node)firstChild_; 602 | while (child) { 603 | child.compactOuterHTML(app); 604 | child = child.next_; 605 | } 606 | } 607 | 608 | void outerHTML(Appender)(ref Appender app) const { 609 | final switch (type) with (NodeTypes) { 610 | case Element: 611 | app.put('<'); 612 | app.put(tag_); 613 | 614 | foreach(HTMLString attr, HTMLString value; attrs_) { 615 | app.put(' '); 616 | app.put(attr); 617 | 618 | if (value.length) { 619 | if (value.requiresQuotes) { 620 | app.put("=\""); 621 | app.writeQuotesEscaped(value); 622 | app.put("\""); 623 | } else { 624 | app.put('='); 625 | app.put(value); 626 | } 627 | } 628 | } 629 | 630 | if (isVoidElement) { 631 | app.put(">"); 632 | } else { 633 | app.put('>'); 634 | if (isScriptOrStyleElement) { 635 | if (firstChild_) 636 | app.put(firstChild_.tag_); 637 | } else { 638 | innerHTML(app); 639 | } 640 | app.put("'); 643 | } 644 | break; 645 | case Text: 646 | app.put(tag_); 647 | break; 648 | case Comment: 649 | app.put(""); 652 | break; 653 | case CDATA: 654 | app.put(""); 657 | break; 658 | case Declaration: 659 | app.put(""); 662 | break; 663 | case ProcessingInstruction: 664 | app.put(""); 667 | break; 668 | } 669 | } 670 | 671 | void compactOuterHTML(Appender)(ref Appender app) const { 672 | final switch (type) with (NodeTypes) { 673 | case Element: 674 | app.put('<'); 675 | app.put(tag_); 676 | 677 | foreach (HTMLString attr, HTMLString value; attrs_) { 678 | app.put(' '); 679 | app.put(attr); 680 | 681 | if (value.length) { 682 | if (value.requiresQuotes) { 683 | app.put("=\""); 684 | app.writeQuotesEscaped(value); 685 | app.put("\""); 686 | } else { 687 | app.put('='); 688 | app.put(value); 689 | } 690 | } 691 | } 692 | 693 | if (isVoidElement) { 694 | app.put(">"); 695 | } else { 696 | app.put('>'); 697 | if (isScriptOrStyleElement) { 698 | if (firstChild_) 699 | app.put(firstChild_.tag_.strip()); 700 | } else { 701 | compactInnerHTML(app); 702 | } 703 | app.put("'); 706 | } 707 | break; 708 | case Text: 709 | auto ptr = tag_.ptr; 710 | const end = ptr + tag_.length; 711 | 712 | if (tag_.isAllWhite()) { 713 | size_t aroundCount; 714 | Node[2] around; 715 | 716 | around.ptr[aroundCount] = cast(Node)prev_; 717 | if (!around.ptr[aroundCount]) 718 | around.ptr[aroundCount] = cast(Node)parent_; 719 | if (around.ptr[aroundCount]) 720 | ++aroundCount; 721 | around.ptr[aroundCount] = cast(Node)next_; 722 | if (!around.ptr[aroundCount]) 723 | around.ptr[aroundCount] = cast(Node)parent_; 724 | if (around.ptr[aroundCount] && (!aroundCount || (around.ptr[aroundCount] !is around.ptr[aroundCount - 1]))) 725 | ++aroundCount; 726 | 727 | auto tagsMatch = true; 728 | Laround: foreach (i; 0..aroundCount) { 729 | if (around.ptr[i].isElementNode) { 730 | if (!around.ptr[i].isBlockElement) { 731 | tagsMatch = false; 732 | break Laround; 733 | } 734 | } 735 | } 736 | 737 | if (!tagsMatch && (ptr != end)) 738 | app.put(*ptr); 739 | } else { 740 | auto space = false; 741 | while (ptr != end) { 742 | auto ch = *ptr++; 743 | if (isWhite(ch)) { 744 | if (space) 745 | continue; 746 | space = true; 747 | } else { 748 | space = false; 749 | } 750 | app.put(ch); 751 | } 752 | } 753 | break; 754 | case Comment: 755 | auto stripped = tag_.strip(); 756 | if (!stripped.empty() && (tag_.front() == '[')) { 757 | app.put(""); 760 | } 761 | break; 762 | case CDATA: 763 | app.put(""); 766 | break; 767 | case Declaration: 768 | app.put(""); 771 | break; 772 | case ProcessingInstruction: 773 | app.put(""); 776 | break; 777 | } 778 | } 779 | 780 | void toString(Appender)(ref Appender app) const { 781 | final switch (type) with (NodeTypes) { 782 | case Element: 783 | app.put('<'); 784 | app.put(tag_); 785 | 786 | foreach(HTMLString attr, HTMLString value; attrs_) { 787 | app.put(' '); 788 | app.put(attr); 789 | 790 | if (value.length) { 791 | app.put("=\""); 792 | writeHTMLEscaped!(Yes.escapeQuotes)(app, value); 793 | app.put("\""); 794 | } 795 | } 796 | if (isVoidElement) { 797 | app.put(">"); 798 | } else { 799 | app.put("/>"); 800 | } 801 | break; 802 | case Text: 803 | writeHTMLEscaped!(No.escapeQuotes)(app, tag_); 804 | break; 805 | case Comment: 806 | app.put(""); 809 | break; 810 | case CDATA: 811 | app.put(""); 814 | break; 815 | case Declaration: 816 | app.put(""); 819 | break; 820 | case ProcessingInstruction: 821 | app.put(""); 824 | break; 825 | } 826 | } 827 | 828 | override string toString() const { 829 | Appender!HTMLString app; 830 | toString(app); 831 | return cast(string)app.data; 832 | } 833 | 834 | @property auto parent() const { 835 | return parent_; 836 | } 837 | 838 | @property auto parent() { 839 | return parent_; 840 | } 841 | 842 | @property auto firstChild() const { 843 | return firstChild_; 844 | } 845 | 846 | @property auto firstChild() { 847 | return firstChild_; 848 | } 849 | 850 | @property auto lastChild() const { 851 | return lastChild_; 852 | } 853 | 854 | @property auto lastChild() { 855 | return lastChild_; 856 | } 857 | 858 | @property auto previousSibling() const { 859 | return prev_; 860 | } 861 | 862 | @property auto previousSibling() { 863 | return prev_; 864 | } 865 | 866 | @property auto nextSibling() const { 867 | return next_; 868 | } 869 | 870 | @property auto nextSibling() { 871 | return next_; 872 | } 873 | 874 | @property auto children() const { 875 | return ChildrenForward!(const(Node))(firstChild_); 876 | } 877 | 878 | @property auto children() { 879 | return ChildrenForward!Node(firstChild_); 880 | } 881 | 882 | @property auto attrs() const { 883 | return attrs_; 884 | } 885 | 886 | @property auto attrs() { 887 | return attrs_; 888 | } 889 | 890 | auto find(HTMLString selector) const { 891 | return document_.querySelectorAll(selector, this); 892 | } 893 | 894 | auto find(HTMLString selector) { 895 | return document_.querySelectorAll(selector, this); 896 | } 897 | 898 | auto find(Selector selector) const { 899 | return document_.querySelectorAll(selector, this); 900 | } 901 | 902 | auto find(Selector selector) { 903 | return document_.querySelectorAll(selector, this); 904 | } 905 | 906 | auto closest(HTMLString selector) const { 907 | auto rules = Selector.parse(selector); 908 | return closest(rules); 909 | } 910 | 911 | Node closest(HTMLString selector) { 912 | auto rules = Selector.parse(selector); 913 | return closest(rules); 914 | } 915 | 916 | auto closest(Selector selector) const { 917 | if (selector.matches(this)) 918 | return this; 919 | 920 | foreach (node; ancestors) { 921 | if (selector.matches(node)) 922 | return node; 923 | } 924 | return null; 925 | } 926 | 927 | Node closest(Selector selector) { 928 | if (selector.matches(this)) 929 | return this; 930 | 931 | foreach (node; ancestors) { 932 | if (selector.matches(node)) 933 | return node; 934 | } 935 | return null; 936 | } 937 | 938 | @property auto ancestors() const { 939 | return AncestorsForward!(const(Node))(parent_); 940 | } 941 | 942 | @property auto ancestors() { 943 | return AncestorsForward!Node(parent_); 944 | } 945 | 946 | @property auto descendants() const { 947 | return DescendantsForward!(const(Node))(this); 948 | } 949 | 950 | @property auto descendants() { 951 | return DescendantsForward!Node(this); 952 | } 953 | 954 | @property isSelfClosing() const { 955 | return (flags_ & Flags.SelfClosing) != 0; 956 | } 957 | 958 | @property isVoidElement() const { 959 | return (flags_ & Flags.VoidElement) != 0; 960 | } 961 | 962 | @property isBlockElement() const { 963 | return (flags_ & Flags.BlockElement) != 0; 964 | } 965 | 966 | @property isScriptElement() const { 967 | return (flags_ & Flags.ScriptElement) != 0; 968 | } 969 | 970 | @property isStyleElement() const { 971 | return (flags_ & Flags.StyleElement) != 0; 972 | } 973 | 974 | @property isScriptOrStyleElement() const { 975 | return (flags_ & (Flags.ScriptElement | Flags.StyleElement)) != 0; 976 | } 977 | 978 | @property bool hasID() const { 979 | return (flags_ & Flags.HasID) != 0; 980 | } 981 | 982 | @property isElementNode() const { 983 | return type == NodeTypes.Element; 984 | } 985 | 986 | @property isTextNode() const { 987 | return type == NodeTypes.Text; 988 | } 989 | 990 | @property isCommentNode() const { 991 | return type == NodeTypes.Comment; 992 | } 993 | 994 | @property isCDATANode() const { 995 | return type == NodeTypes.CDATA; 996 | } 997 | 998 | @property isDeclarationNode() const { 999 | return type == NodeTypes.Declaration; 1000 | } 1001 | 1002 | @property isProcessingInstructionNode() const { 1003 | return type == NodeTypes.ProcessingInstruction; 1004 | } 1005 | 1006 | Node clone(Document document) const { 1007 | auto node = document.allocNode(tag_, flags_); 1008 | node.tagHash_ = tagHash_; 1009 | node.flags_ = flags_; 1010 | 1011 | foreach (HTMLString attr, HTMLString value; attrs_) 1012 | node.attrs_[attr] = value; 1013 | 1014 | Node current = cast(Node)firstChild_; 1015 | 1016 | while (current !is null) { 1017 | auto newChild = current.clone(document); 1018 | node.appendChild(newChild); 1019 | current = current.next_; 1020 | } 1021 | return node; 1022 | } 1023 | 1024 | Node clone() const { 1025 | return document_.cloneNode(this); 1026 | } 1027 | 1028 | package: 1029 | enum TypeMask = 0x7UL; 1030 | enum TypeShift = 0UL; 1031 | enum FlagsBit = TypeMask + 1; 1032 | enum Flags { 1033 | SelfClosing = FlagsBit << 1, 1034 | VoidElement = FlagsBit << 2, 1035 | BlockElement = FlagsBit << 3, 1036 | ScriptElement = FlagsBit << 4, 1037 | StyleElement = FlagsBit << 5, 1038 | 1039 | HasID = FlagsBit << 6, 1040 | } 1041 | 1042 | size_t flags_; 1043 | hash_t tagHash_; 1044 | HTMLString tag_; // when Text flag is set, will contain the text itself 1045 | HTMLString[HTMLString] attrs_; 1046 | 1047 | Node parent_; 1048 | Node firstChild_; 1049 | Node lastChild_; 1050 | 1051 | // siblings 1052 | Node prev_; 1053 | Node next_; 1054 | 1055 | Document document_; 1056 | } 1057 | 1058 | 1059 | auto createDocument(size_t Options = DOMCreateOptions.Default)(HTMLString source, IAllocator alloc = theAllocator) { 1060 | enum parserOptions = ((Options & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); 1061 | static assert((Options & DOMCreateOptions.ValidateAll) == 0, "requested validation with no error callable"); 1062 | 1063 | auto document = createDocument(alloc); 1064 | auto builder = DOMBuilder!(Document, Options)(document); 1065 | 1066 | parseHTML!(typeof(builder), parserOptions)(source, builder); 1067 | return document; 1068 | } 1069 | 1070 | 1071 | auto createDocument(size_t Options = DOMCreateOptions.Default | DOMCreateOptions.ValidateAll)(HTMLString source, ValidationErrorCallable errorCallable, IAllocator alloc = theAllocator) { 1072 | enum parserOptions = ((Options & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); 1073 | static assert((Options & DOMCreateOptions.ValidateAll) != 0, "error callable but validation not requested"); 1074 | 1075 | auto document = createDocument(alloc); 1076 | auto builder = DOMBuilder!(Document, Options)(document, errorCallable); 1077 | 1078 | parseHTML!(typeof(builder), parserOptions)(source, builder); 1079 | return document; 1080 | } 1081 | 1082 | 1083 | unittest { 1084 | auto doc = createDocument(" \n\r\f\t "); 1085 | assert(doc.root.compactOuterHTML == ""); 1086 | doc = createDocument("

"); 1087 | assert(doc.root.compactOuterHTML == "

"); 1088 | } 1089 | 1090 | unittest { 1091 | auto doc = createDocument(` `); 1092 | assert(doc.root.outerHTML == ""); 1093 | doc = createDocument!(DOMCreateOptions.None)(` `); 1094 | assert(doc.root.outerHTML == ` `); 1095 | doc = createDocument(``); 1096 | assert(doc.root.outerHTML == ``, doc.root.outerHTML); 1097 | doc = createDocument(``); 1098 | assert(doc.root.outerHTML == ``, doc.root.outerHTML); 1099 | } 1100 | 1101 | unittest { 1102 | const doc = createDocument(`
"К"ириллица
`); 1103 | assert(doc.root.html == `
"К"ириллица
`); 1104 | } 1105 | 1106 | unittest { 1107 | // void elements should not be self-closed 1108 | auto doc = createDocument(`
`); 1109 | assert(doc.root.outerHTML == `
`, doc.root.outerHTML); 1110 | doc = createDocument(``); 1111 | assert(doc.root.outerHTML == ``, doc.root.outerHTML); 1112 | doc = createDocument(`
`); 1113 | assert(doc.root.outerHTML == `
`, doc.root.outerHTML); 1114 | } 1115 | 1116 | // toString prints elements with content as 1117 | unittest { 1118 | // self-closed element w/o content 1119 | auto doc = createDocument(``); 1120 | assert(doc.root.firstChild.toString == ``, doc.root.firstChild.toString); 1121 | // elements w/ content 1122 | doc = createDocument(``); 1123 | assert(doc.root.firstChild.toString == ``, doc.root.firstChild.toString); 1124 | doc = createDocument(`
`); 1125 | assert(doc.root.firstChild.toString == `
`, doc.root.firstChild.toString); 1126 | // void element 1127 | doc = createDocument(`
`); 1128 | assert(doc.root.firstChild.toString == `
`, doc.root.firstChild.toString); 1129 | // "invalid" self-closed void element 1130 | doc = createDocument(`
`); 1131 | assert(doc.root.firstChild.toString == `
`, doc.root.firstChild.toString); 1132 | } 1133 | 1134 | unittest { 1135 | const doc = createDocument(`
 
`); 1136 | assert(doc.root.find("html").front.outerHTML == "
"); 1137 | assert(doc.root.find("html").front.find("div").front.outerHTML == "
"); 1138 | assert(doc.root.find("body").front.outerHTML == "
"); 1139 | assert(doc.root.find("body").front.closest("body").outerHTML == "
"); // closest() tests self 1140 | assert(doc.root.find("body").front.closest("html").outerHTML == "
"); 1141 | } 1142 | 1143 | unittest { 1144 | const doc = createDocument(``); 1145 | assert(doc.root.firstChild.firstChild.firstChild.isCDATANode); 1146 | assert(doc.root.html == ``); 1147 | } 1148 | 1149 | 1150 | static auto createDocument(IAllocator alloc = theAllocator) { 1151 | auto document = new Document(alloc); 1152 | document.root(document.createElement("root")); 1153 | return document; 1154 | } 1155 | 1156 | 1157 | class Document { 1158 | private this(IAllocator alloc) { 1159 | alloc_ = alloc; 1160 | } 1161 | 1162 | auto createElement(HTMLString tagName, Node parent = null) { 1163 | auto node = allocNode(tagName, NodeTypes.Element << Node.TypeShift); 1164 | node.tagHash_ = tagHashOf(tagName); 1165 | node.flags_ |= elementFlags(node.tagHash_); 1166 | if (parent) 1167 | parent.appendChild(node); 1168 | return node; 1169 | } 1170 | 1171 | auto createTextNode(HTMLString text, Node parent = null) { 1172 | auto node = allocNode(text, NodeTypes.Text << Node.TypeShift); 1173 | if (parent) 1174 | parent.appendChild(node); 1175 | return node; 1176 | } 1177 | 1178 | auto createCommentNode(HTMLString comment, Node parent = null) { 1179 | auto node = allocNode(comment, NodeTypes.Comment << Node.TypeShift); 1180 | if (parent) 1181 | parent.appendChild(node); 1182 | return node; 1183 | } 1184 | 1185 | auto createCDATANode(HTMLString cdata, Node parent = null) { 1186 | auto node = allocNode(cdata, NodeTypes.CDATA << Node.TypeShift); 1187 | if (parent) 1188 | parent.appendChild(node); 1189 | return node; 1190 | } 1191 | 1192 | auto createDeclarationNode(HTMLString data, Node parent = null) { 1193 | auto node = allocNode(data, NodeTypes.Declaration << Node.TypeShift); 1194 | if (parent) 1195 | parent.appendChild(node); 1196 | return node; 1197 | } 1198 | 1199 | auto createProcessingInstructionNode(HTMLString data, Node parent = null) { 1200 | auto node = allocNode(data, NodeTypes.ProcessingInstruction << Node.TypeShift); 1201 | if (parent) 1202 | parent.appendChild(node); 1203 | return node; 1204 | } 1205 | 1206 | Node cloneNode(const(Node) other) const { 1207 | return other.clone(cast(Document)this); 1208 | } 1209 | 1210 | Document clone(IAllocator alloc = theAllocator) { 1211 | Document other = new Document(alloc); 1212 | other.root(other.cloneNode(this.root_)); 1213 | return other; 1214 | } 1215 | 1216 | @property auto root() { 1217 | return root_; 1218 | } 1219 | 1220 | @property auto root() const { 1221 | return root_; 1222 | } 1223 | 1224 | @property void root(Node root) { 1225 | if (root && (root.document_.alloc_ != alloc_)) 1226 | alloc_ = root.document_.alloc_; 1227 | root_ = root; 1228 | } 1229 | 1230 | @property auto nodes() const { 1231 | return DescendantsForward!(const(Node))(root_); 1232 | } 1233 | 1234 | @property auto nodes() { 1235 | return DescendantsForward!Node(root_); 1236 | } 1237 | 1238 | @property auto elements() const { 1239 | return DescendantsForward!(const(Node), onlyElements)(root_); 1240 | } 1241 | 1242 | @property auto elements() { 1243 | return DescendantsForward!(Node, onlyElements)(root_); 1244 | } 1245 | 1246 | @property auto elementsByTagName(HTMLString tag) const { 1247 | const tagHash = tagHashOf(tag); 1248 | return DescendantsForward!(const(Node), (a) { return a.isElementNode && (a.tagHash == tagHash); })(root_); 1249 | } 1250 | 1251 | @property auto elementsByTagName(HTMLString tag) { 1252 | const tagHash = tagHashOf(tag); 1253 | return DescendantsForward!(Node, (a) { return a.isElementNode && (a.tagHash == tagHash); })(root_); 1254 | } 1255 | 1256 | const(Node) querySelector(HTMLString selector, Node context = null) const { 1257 | auto rules = Selector.parse(selector); 1258 | return querySelector(rules, context); 1259 | } 1260 | 1261 | Node querySelector(HTMLString selector, Node context = null) { 1262 | auto rules = Selector.parse(selector); 1263 | return querySelector(rules, context); 1264 | } 1265 | 1266 | const(Node) querySelector(Selector selector, const(Node) context = null) const { 1267 | auto top = context ? context : root_; 1268 | 1269 | foreach(node; DescendantsForward!(const(Node), onlyElements)(top)) { 1270 | if (selector.matches(node)) 1271 | return node; 1272 | } 1273 | return null; 1274 | } 1275 | 1276 | Node querySelector(Selector selector, Node context = null) { 1277 | auto top = context ? context : root_; 1278 | 1279 | foreach(node; DescendantsForward!(Node, onlyElements)(top)) { 1280 | if (selector.matches(node)) 1281 | return node; 1282 | } 1283 | return null; 1284 | } 1285 | 1286 | alias QuerySelectorAllResult = QuerySelectorMatcher!(Node, DescendantsForward!Node); 1287 | alias QuerySelectorAllConstResult = QuerySelectorMatcher!(const(Node), DescendantsForward!(const(Node))); 1288 | 1289 | QuerySelectorAllResult querySelectorAll(HTMLString selector, Node context = null) { 1290 | auto rules = Selector.parse(selector); 1291 | return querySelectorAll(rules, context); 1292 | } 1293 | 1294 | QuerySelectorAllConstResult querySelectorAll(HTMLString selector, const(Node) context = null) const { 1295 | auto rules = Selector.parse(selector); 1296 | return querySelectorAll(rules, context); 1297 | } 1298 | 1299 | QuerySelectorAllConstResult querySelectorAll(Selector selector, const(Node) context = null) const { 1300 | auto top = context ? context : root_; 1301 | return QuerySelectorMatcher!(const(Node), DescendantsForward!(const(Node)))(selector, DescendantsForward!(const(Node))(top)); 1302 | } 1303 | 1304 | QuerySelectorAllResult querySelectorAll(Selector selector, Node context = null) { 1305 | auto top = context ? context : root_; 1306 | return QuerySelectorMatcher!(Node, DescendantsForward!Node)(selector, DescendantsForward!Node(top)); 1307 | } 1308 | 1309 | void toString(Appender)(ref Appender app) const { 1310 | root_.outerHTML(app); 1311 | } 1312 | 1313 | override string toString() const { 1314 | auto app = appender!HTMLString; 1315 | root_.outerHTML(app); 1316 | return cast(string)app.data; 1317 | } 1318 | 1319 | auto allocNode()(HTMLString tag, size_t flags) { 1320 | enum NodeSize = __traits(classInstanceSize, Node); 1321 | auto ptr = cast(Node)alloc_.allocate(NodeSize).ptr; 1322 | (cast(void*)ptr)[0..NodeSize] = typeid(Node).initializer[]; 1323 | ptr.__ctor(tag, this, flags); 1324 | return ptr; 1325 | } 1326 | 1327 | void destroyNode(Node node) { 1328 | assert(node.firstChild_ is null); 1329 | alloc_.dispose(node); 1330 | } 1331 | 1332 | private: 1333 | Node root_; 1334 | IAllocator alloc_; 1335 | } 1336 | 1337 | 1338 | unittest { 1339 | const(char)[] src = `text`; 1340 | auto doc = createDocument(src); 1341 | assert(doc.root.html == src, doc.root.html); 1342 | 1343 | const(char)[] srcq = `text`; 1344 | auto docq = createDocument(srcq); 1345 | assert(docq.root.html == srcq, docq.root.html); 1346 | 1347 | // basic cloning 1348 | auto cloned = doc.cloneNode(doc.root); 1349 | assert(cloned.html == src, cloned.html); 1350 | assert(doc.root.html == src, cloned.html); 1351 | 1352 | assert(!cloned.find("child").empty); 1353 | assert(!cloned.find("parent").empty); 1354 | 1355 | // clone mutation 1356 | auto child = cloned.find("child").front.clone; 1357 | child.attr("attr", "test"); 1358 | cloned.find("parent").front.appendChild(child); 1359 | assert(cloned.html == `text`, cloned.html); 1360 | assert(doc.root.html == src, doc.root.html); 1361 | 1362 | child.text = "text"; 1363 | assert(cloned.html == `texttext`, cloned.html); 1364 | assert(doc.root.html == src, doc.root.html); 1365 | 1366 | // document cloning 1367 | auto docc = doc.clone; 1368 | assert(docc.root.html == doc.root.html, docc.root.html); 1369 | } 1370 | 1371 | 1372 | static struct DOMBuilder(Document, size_t Options) { 1373 | enum { 1374 | Validate = (Options & DOMCreateOptions.ValidateAll) != 0, 1375 | ValidateClosed = (Options & DOMCreateOptions.ValidateClosed) != 0, 1376 | ValidateSelfClosing = (Options & DOMCreateOptions.ValidateSelfClosing) != 0, 1377 | ValidateDuplicateAttr = (Options & DOMCreateOptions.ValidateDuplicateAttr) != 0, 1378 | } 1379 | 1380 | static if (Validate) { 1381 | this(Document document, ValidationErrorCallable errorCallable, Node parent = null) { 1382 | document_ = document; 1383 | element_ = parent ? parent : document.root; 1384 | error_ = errorCallable; 1385 | } 1386 | } else { 1387 | this(Document document, Node parent = null) { 1388 | document_ = document; 1389 | element_ = parent ? parent : document.root; 1390 | } 1391 | } 1392 | 1393 | void onText(HTMLString data) { 1394 | if (data.ptr == (text_.ptr + text_.length)) { 1395 | text_ = text_.ptr[0..text_.length + data.length]; 1396 | } else { 1397 | text_ ~= data; 1398 | } 1399 | } 1400 | 1401 | void onSelfClosing() { 1402 | if (element_.isVoidElement) { 1403 | element_.flags_ |= Node.Flags.SelfClosing; 1404 | } else { 1405 | static if (ValidateSelfClosing) { 1406 | error_(ValidationError.SelfClosingNonVoidElement, element_.tag_, null); 1407 | } 1408 | } 1409 | element_ = element_.parent_; 1410 | } 1411 | 1412 | void onOpenStart(HTMLString data) { 1413 | if (!text_.empty) { 1414 | element_.appendText(text_); 1415 | text_.length = 0; 1416 | } 1417 | 1418 | element_ = document_.createElement(data, element_); 1419 | element_.flags_ |= elementFlags(element_.tagHash_); 1420 | } 1421 | 1422 | void onOpenEnd(HTMLString data) { 1423 | // void elements have neither content nor a closing tag, so 1424 | // we're done w/ them on the end of the open tag 1425 | if (element_.isVoidElement) 1426 | element_ = element_.parent_; 1427 | } 1428 | 1429 | void onClose(HTMLString data) { 1430 | if (!text_.empty) { 1431 | if (element_) { 1432 | element_.appendText(text_); 1433 | text_.length = 0; 1434 | } else { 1435 | document_.root.appendText(text_); 1436 | } 1437 | } 1438 | 1439 | if (element_) { 1440 | assert(!element_.isTextNode); 1441 | if (element_.tag.equalsCI(data)) { 1442 | element_ = element_.parent_; 1443 | } else { 1444 | auto element = element_.parent_; 1445 | while (element) { 1446 | if (element.tag.equalsCI(data)) { 1447 | static if (ValidateClosed) { 1448 | error_(ValidationError.MismatchingClose, data, element_.tag); 1449 | } 1450 | element_ = element.parent_; 1451 | break; 1452 | } 1453 | element = element.parent_; 1454 | } 1455 | static if (ValidateClosed) { 1456 | if (!element) 1457 | error_(ValidationError.StrayClose, data, null); 1458 | } 1459 | } 1460 | } else { 1461 | static if (ValidateClosed) { 1462 | error_(ValidationError.StrayClose, data, null); 1463 | } 1464 | } 1465 | } 1466 | 1467 | void onAttrName(HTMLString data) { 1468 | attr_ = data; 1469 | state_ = States.Attr; 1470 | } 1471 | 1472 | void onAttrEnd() { 1473 | if (!attr_.empty) { 1474 | static if (ValidateDuplicateAttr) { 1475 | if (attr_ in element_.attrs_) 1476 | error_(ValidationError.DuplicateAttr, element_.tag_, attr_); 1477 | } 1478 | element_.attr(attr_, value_); 1479 | } 1480 | value_.length = 0; 1481 | attr_.length = 0; 1482 | state_ = States.Global; 1483 | } 1484 | 1485 | void onAttrValue(HTMLString data) { 1486 | if (data.ptr == (value_.ptr + value_.length)) { 1487 | value_ = value_.ptr[0..value_.length + data.length]; 1488 | } else { 1489 | value_ ~= data; 1490 | } 1491 | } 1492 | 1493 | void onComment(HTMLString data) { 1494 | document_.createCommentNode(data, element_); 1495 | } 1496 | 1497 | void onDeclaration(HTMLString data) { 1498 | document_.createDeclarationNode(data, element_); 1499 | } 1500 | 1501 | void onProcessingInstruction(HTMLString data) { 1502 | document_.createProcessingInstructionNode(data, element_); 1503 | } 1504 | 1505 | void onCDATA(HTMLString data) { 1506 | document_.createCDATANode(data, element_); 1507 | } 1508 | 1509 | void onDocumentEnd() { 1510 | if (!text_.empty) { 1511 | if (element_) { 1512 | element_.appendText(text_); 1513 | text_.length = 0; 1514 | } else { 1515 | document_.root.appendText(text_); 1516 | } 1517 | } 1518 | 1519 | static if (ValidateClosed) { 1520 | while (element_ && element_ !is document_.root_) { 1521 | error_(ValidationError.MissingClose, element_.tag, null); 1522 | element_ = element_.parent_; 1523 | } 1524 | } 1525 | } 1526 | 1527 | void onNamedEntity(HTMLString data) { 1528 | } 1529 | 1530 | void onNumericEntity(HTMLString data) { 1531 | } 1532 | 1533 | void onHexEntity(HTMLString data) { 1534 | } 1535 | 1536 | void onEntity(HTMLString data, HTMLString decoded) { 1537 | if (state_ == States.Global) { 1538 | text_ ~= decoded; 1539 | } else { 1540 | value_ ~= decoded; 1541 | } 1542 | } 1543 | 1544 | private: 1545 | Document document_; 1546 | Node element_; 1547 | States state_; 1548 | static if (Validate) { 1549 | ValidationErrorCallable error_; 1550 | } 1551 | 1552 | enum States { 1553 | Global = 0, 1554 | Attr, 1555 | } 1556 | 1557 | HTMLString attr_; 1558 | HTMLString value_; 1559 | HTMLString text_; 1560 | } 1561 | 1562 | 1563 | 1564 | unittest { 1565 | static struct Error { 1566 | ValidationError error; 1567 | HTMLString tag; 1568 | HTMLString related; 1569 | } 1570 | 1571 | Error[] errors; 1572 | void errorHandler(ValidationError error, HTMLString tag, HTMLString related) { 1573 | errors ~= Error(error, tag, related); 1574 | } 1575 | 1576 | errors.length = 0; 1577 | auto doc = createDocument(`
`, &errorHandler); 1578 | assert(errors == [Error(ValidationError.MissingClose, "div")]); 1579 | 1580 | errors.length = 0; 1581 | doc = createDocument(`
`, &errorHandler); 1582 | assert(errors == [Error(ValidationError.StrayClose, "div")]); 1583 | 1584 | errors.length = 0; 1585 | doc = createDocument(`
`, &errorHandler); 1586 | assert(errors == [Error(ValidationError.MismatchingClose, "span", "div")]); 1587 | 1588 | errors.length = 0; 1589 | doc = createDocument(`
`, &errorHandler); 1590 | assert(errors == [Error(ValidationError.SelfClosingNonVoidElement, "div")]); 1591 | 1592 | errors.length = 0; 1593 | doc = createDocument(`
`, &errorHandler); 1594 | assert(errors == [Error(ValidationError.StrayClose, "hr")]); 1595 | 1596 | errors.length = 0; 1597 | doc = createDocument(``, &errorHandler); 1598 | assert(errors == [Error(ValidationError.DuplicateAttr, "span", "id"), Error(ValidationError.DuplicateAttr, "span", "class")]); 1599 | } 1600 | 1601 | 1602 | private struct Rule { 1603 | enum Flags : ushort { 1604 | HasTag = 1 << 0, 1605 | HasID = 1 << 1, 1606 | HasAttr = 1 << 2, 1607 | HasPseudo = 1 << 3, 1608 | CaseSensitive = 1 << 4, 1609 | HasAny = 1 << 5, 1610 | } 1611 | 1612 | enum MatchType : ubyte { 1613 | None = 0, 1614 | Set, 1615 | Exact, 1616 | ContainWord, 1617 | Contain, 1618 | Begin, 1619 | BeginHyphen, 1620 | End, 1621 | } 1622 | 1623 | enum Relation : ubyte { 1624 | None = 0, 1625 | Descendant, 1626 | Child, 1627 | DirectAdjacent, 1628 | IndirectAdjacent, 1629 | } 1630 | 1631 | bool matches(NodeType)(NodeType element) const { 1632 | if (flags_ == 0) 1633 | return false; 1634 | 1635 | if (flags_ & Flags.HasTag) { 1636 | if (tagHash_ != element.tagHash) 1637 | return false; 1638 | } 1639 | 1640 | if (flags_ & Flags.HasID) { 1641 | if (value_.empty || !element.hasID) return false; 1642 | if (value_ != element.id()) return false; 1643 | } 1644 | 1645 | if (flags_ & Flags.HasAttr) { 1646 | auto cs = (flags_ & Flags.CaseSensitive) != 0; 1647 | final switch (match_) with (MatchType) { 1648 | case None: 1649 | break; 1650 | case Set: 1651 | if (element.attr(attr_) == null) 1652 | return false; 1653 | break; 1654 | case Exact: 1655 | if (value_.empty) return false; 1656 | auto attr = element.attr(attr_); 1657 | if (!attr || (cs ? (value_ != attr) : !value_.equalsCI(attr))) 1658 | return false; 1659 | break; 1660 | case Contain: 1661 | if (value_.empty) return false; 1662 | auto attr = element.attr(attr_); 1663 | if (!attr || ((attr.indexOf(value_, cs ? CaseSensitive.yes : CaseSensitive.no)) == -1)) 1664 | return false; 1665 | break; 1666 | case ContainWord: 1667 | if (value_.empty) return false; 1668 | auto attr = element.attr(attr_); 1669 | if (!attr) 1670 | return false; 1671 | 1672 | size_t start = 0; 1673 | while (true) { 1674 | auto index = attr.indexOf(value_, start, cs ? CaseSensitive.yes : CaseSensitive.no); 1675 | if (index == -1) 1676 | return false; 1677 | if (index && !isWhite(attr[index - 1])) 1678 | return false; 1679 | if ((index + value_.length == attr.length) || isWhite(attr[index + value_.length])) 1680 | break; 1681 | start = index + 1; 1682 | } 1683 | break; 1684 | case Begin: 1685 | if (value_.empty) return false; 1686 | auto attr = element.attr(attr_); 1687 | if (!attr || ((attr.indexOf(value_, cs ? CaseSensitive.yes : CaseSensitive.no)) != 0)) 1688 | return false; 1689 | break; 1690 | case End: 1691 | if (value_.empty) return false; 1692 | auto attr = element.attr(attr_); 1693 | if (!attr || ((attr.lastIndexOf(value_, cs ? CaseSensitive.yes : CaseSensitive.no)) != (attr.length - value_.length))) 1694 | return false; 1695 | break; 1696 | case BeginHyphen: 1697 | if (value_.empty) return false; 1698 | auto attr = element.attr(attr_); 1699 | if (!attr || ((attr.indexOf(value_, cs ? CaseSensitive.yes : CaseSensitive.no)) != 0) || ((attr.length > value_.length) && (attr[value_.length] != '-'))) 1700 | return false; 1701 | break; 1702 | } 1703 | } 1704 | 1705 | if (flags_ & Flags.HasPseudo) { 1706 | switch (pseudo_) { 1707 | case quickHashOf("checked"): 1708 | if (!element.hasAttr("checked")) 1709 | return false; 1710 | break; 1711 | 1712 | case quickHashOf("enabled"): 1713 | if (element.hasAttr("disabled")) 1714 | return false; 1715 | break; 1716 | 1717 | case quickHashOf("disabled"): 1718 | if (!element.hasAttr("disabled")) 1719 | return false; 1720 | break; 1721 | 1722 | case quickHashOf("empty"): 1723 | if (element.firstChild_) 1724 | return false; 1725 | break; 1726 | 1727 | case quickHashOf("optional"): 1728 | if (element.hasAttr("required")) 1729 | return false; 1730 | break; 1731 | 1732 | case quickHashOf("read-only"): 1733 | if (!element.hasAttr("readonly")) 1734 | return false; 1735 | break; 1736 | 1737 | case quickHashOf("read-write"): 1738 | if (element.hasAttr("readonly")) 1739 | return false; 1740 | break; 1741 | 1742 | case quickHashOf("required"): 1743 | if (!element.hasAttr("required")) 1744 | return false; 1745 | break; 1746 | 1747 | case quickHashOf("lang"): 1748 | if (element.attr("lang") != pseudoArg_) 1749 | return false; 1750 | break; 1751 | 1752 | case quickHashOf("first-child"): 1753 | if (!element.parent_ || (element.parent_.firstChild !is element)) 1754 | return false; 1755 | break; 1756 | 1757 | case quickHashOf("last-child"): 1758 | if (!element.parent_ || (element.parent_.lastChild !is element)) 1759 | return false; 1760 | break; 1761 | 1762 | case quickHashOf("first-of-type"): 1763 | Node sibling = element.previousSibling; 1764 | while (sibling) { 1765 | if (sibling.isElementNode && sibling.tag.equalsCI(element.tag)) 1766 | return false; 1767 | sibling = sibling.previousSibling; 1768 | } 1769 | break; 1770 | 1771 | case quickHashOf("last-of-type"): 1772 | auto sibling = element.nextSibling; 1773 | while (sibling) { 1774 | if (sibling.isElementNode && sibling.tag.equalsCI(element.tag)) 1775 | return false; 1776 | sibling = sibling.nextSibling; 1777 | } 1778 | break; 1779 | 1780 | case quickHashOf("nth-child"): 1781 | auto ith = 1; 1782 | auto sibling = element.previousSibling; 1783 | while (sibling) { 1784 | if (ith > pseudoArgNum_) 1785 | return false; 1786 | if (sibling.isElementNode) 1787 | ++ith; 1788 | sibling = sibling.previousSibling; 1789 | } 1790 | if (ith != pseudoArgNum_) 1791 | return false; 1792 | break; 1793 | 1794 | case quickHashOf("nth-last-child"): 1795 | auto ith = 1; 1796 | auto sibling = element.nextSibling; 1797 | while (sibling) { 1798 | if (ith > pseudoArgNum_) 1799 | return false; 1800 | if (sibling.isElementNode) 1801 | ++ith; 1802 | sibling = sibling.nextSibling; 1803 | } 1804 | if (ith != pseudoArgNum_) 1805 | return false; 1806 | break; 1807 | 1808 | case quickHashOf("nth-of-type"): 1809 | auto ith = 1; 1810 | auto sibling = element.previousSibling; 1811 | while (sibling) { 1812 | if (ith > pseudoArgNum_) 1813 | return false; 1814 | if (sibling.isElementNode && sibling.tag.equalsCI(element.tag)) 1815 | ++ith; 1816 | sibling = sibling.previousSibling; 1817 | } 1818 | if (ith != pseudoArgNum_) 1819 | return false; 1820 | break; 1821 | 1822 | case quickHashOf("nth-last-of-type"): 1823 | auto ith = 1; 1824 | auto sibling = element.nextSibling; 1825 | while (sibling) { 1826 | if (ith > pseudoArgNum_) 1827 | return false; 1828 | if (sibling.isElementNode && sibling.tag.equalsCI(element.tag)) 1829 | ++ith; 1830 | sibling = sibling.nextSibling; 1831 | } 1832 | if (ith != pseudoArgNum_) 1833 | return false; 1834 | break; 1835 | 1836 | case quickHashOf("only-of-type"): 1837 | auto sibling = element.previousSibling; 1838 | while (sibling) { 1839 | if (sibling.isElementNode && sibling.tag.equalsCI(element.tag)) 1840 | return false; 1841 | sibling = sibling.previousSibling; 1842 | } 1843 | sibling = element.nextSibling; 1844 | while (sibling) { 1845 | if (sibling.isElementNode && sibling.tag.equalsCI(element.tag)) 1846 | return false; 1847 | sibling = sibling.nextSibling; 1848 | } 1849 | break; 1850 | 1851 | case quickHashOf("only-child"): 1852 | auto parent = element.parent_; 1853 | if (parent is null) 1854 | return false; 1855 | Node sibling = parent.firstChild_; 1856 | while (sibling) { 1857 | if ((sibling !is element) && sibling.isElementNode) 1858 | return false; 1859 | sibling = sibling.next_; 1860 | } 1861 | break; 1862 | 1863 | default: 1864 | break; 1865 | } 1866 | } 1867 | 1868 | return true; 1869 | } 1870 | 1871 | @property Relation relation() const { 1872 | return relation_; 1873 | } 1874 | 1875 | package: 1876 | ushort flags_; 1877 | MatchType match_; 1878 | Relation relation_; 1879 | hash_t tagHash_; 1880 | size_t pseudo_; 1881 | HTMLString attr_; 1882 | HTMLString value_; 1883 | HTMLString pseudoArg_; 1884 | uint pseudoArgNum_; 1885 | } 1886 | 1887 | 1888 | struct Selector { 1889 | static Selector parse(HTMLString value) { 1890 | enum ParserStates { 1891 | Identifier = 0, 1892 | PostIdentifier, 1893 | Tag, 1894 | Class, 1895 | ID, 1896 | AttrName, 1897 | AttrOp, 1898 | PreAttrValue, 1899 | AttrValueDQ, 1900 | AttrValueSQ, 1901 | AttrValueNQ, 1902 | PostAttrValue, 1903 | Pseudo, 1904 | PseudoArgs, 1905 | Relation, 1906 | } 1907 | 1908 | size_t ids; 1909 | size_t tags; 1910 | size_t classes; 1911 | 1912 | value = value.strip; 1913 | auto source = uninitializedArray!(char[])(value.length + 1); 1914 | source[0..value.length] = value; 1915 | source[$-1] = ' '; // add a padding space to ease parsing 1916 | 1917 | auto selector = Selector(source); 1918 | Rule[] rules; 1919 | ++rules.length; 1920 | 1921 | auto rule = &rules.back; 1922 | 1923 | auto ptr = source.ptr; 1924 | auto end = source.ptr + source.length; 1925 | auto start = ptr; 1926 | 1927 | ParserStates state = ParserStates.Identifier; 1928 | 1929 | while (ptr != end) { 1930 | final switch (state) with (ParserStates) { 1931 | case Identifier: 1932 | if (*ptr == '#') { 1933 | state = ID; 1934 | start = ptr + 1; 1935 | } else if (*ptr == '.') { 1936 | state = Class; 1937 | start = ptr + 1; 1938 | } else if (*ptr == '[') { 1939 | state = AttrName; 1940 | start = ptr + 1; 1941 | } else if (isAlpha(*ptr)) { 1942 | state = Tag; 1943 | start = ptr; 1944 | continue; 1945 | } else if (*ptr == '*') { 1946 | rule.flags_ |= Rule.Flags.HasAny; 1947 | state = PostIdentifier; 1948 | } else if (*ptr == ':') { 1949 | rule.flags_ |= Rule.Flags.HasAny; 1950 | state = PostIdentifier; 1951 | continue; 1952 | } 1953 | break; 1954 | 1955 | case PostIdentifier: 1956 | switch (*ptr) { 1957 | case '#': 1958 | state = ID; 1959 | start = ptr + 1; 1960 | break; 1961 | case '.': 1962 | state = Class; 1963 | start = ptr + 1; 1964 | break; 1965 | case '[': 1966 | state = AttrName; 1967 | start = ptr + 1; 1968 | break; 1969 | case ':': 1970 | state = Pseudo; 1971 | if ((ptr + 1 != end) && (*(ptr + 1) == ':')) 1972 | ++ptr; 1973 | start = ptr + 1; 1974 | break; 1975 | default: 1976 | state = Relation; 1977 | continue; 1978 | } 1979 | break; 1980 | 1981 | case Tag: 1982 | while ((ptr != end) && isAlphaNum(*ptr)) 1983 | ++ptr; 1984 | if (ptr == end) 1985 | continue; 1986 | 1987 | rule.flags_ |= Rule.Flags.HasTag; 1988 | rule.tagHash_ = tagHashOf(start[0..ptr-start]); 1989 | ++tags; 1990 | 1991 | state = PostIdentifier; 1992 | continue; 1993 | 1994 | case Class: 1995 | while ((ptr != end) && (isAlphaNum(*ptr) || (*ptr == '-') || (*ptr == '_'))) 1996 | ++ptr; 1997 | if (ptr == end) 1998 | continue; 1999 | 2000 | rule.flags_ |= Rule.Flags.HasAttr; 2001 | rule.match_ = Rule.MatchType.ContainWord; 2002 | rule.attr_ = "class"; 2003 | rule.value_ = start[0..ptr-start]; 2004 | ++classes; 2005 | 2006 | state = PostIdentifier; 2007 | break; 2008 | 2009 | case ID: 2010 | while ((ptr != end) && (isAlphaNum(*ptr) || (*ptr == '-') || (*ptr == '_'))) 2011 | ++ptr; 2012 | if (ptr == end) 2013 | continue; 2014 | 2015 | rule.flags_ |= Rule.Flags.HasID; 2016 | rule.value_ = start[0..ptr-start]; 2017 | ++ids; 2018 | 2019 | state = PostIdentifier; 2020 | break; 2021 | 2022 | case AttrName: 2023 | while ((ptr != end) && (isAlphaNum(*ptr) || (*ptr == '-') || (*ptr == '_'))) 2024 | ++ptr; 2025 | if (ptr == end) 2026 | continue; 2027 | 2028 | rule.flags_ |= Rule.Flags.HasAttr; 2029 | rule.flags_ |= Rule.Flags.CaseSensitive; 2030 | rule.attr_ = start[0..ptr-start]; 2031 | ++classes; 2032 | 2033 | state = AttrOp; 2034 | continue; 2035 | 2036 | case AttrOp: 2037 | while ((ptr != end) && (isWhite(*ptr))) 2038 | ++ptr; 2039 | if (ptr == end) 2040 | continue; 2041 | 2042 | switch (*ptr) { 2043 | case ']': 2044 | rule.match_ = Rule.MatchType.Set; 2045 | state = PostIdentifier; 2046 | break; 2047 | case '=': 2048 | rule.match_ = Rule.MatchType.Exact; 2049 | state = PreAttrValue; 2050 | break; 2051 | default: 2052 | if ((ptr + 1 != end) && (*(ptr + 1) == '=')) { 2053 | switch (*ptr) { 2054 | case '~': 2055 | rule.match_ = Rule.MatchType.ContainWord; 2056 | break; 2057 | case '^': 2058 | rule.match_ = Rule.MatchType.Begin; 2059 | break; 2060 | case '$': 2061 | rule.match_ = Rule.MatchType.End; 2062 | break; 2063 | case '*': 2064 | rule.match_ = Rule.MatchType.Contain; 2065 | break; 2066 | case '|': 2067 | rule.match_ = Rule.MatchType.BeginHyphen; 2068 | break; 2069 | default: 2070 | rule.flags_ = 0; // error 2071 | ptr = end - 1; 2072 | break; 2073 | } 2074 | 2075 | state = PreAttrValue; 2076 | ++ptr; 2077 | } 2078 | break; 2079 | } 2080 | break; 2081 | 2082 | case PreAttrValue: 2083 | while ((ptr != end) && isWhite(*ptr)) 2084 | ++ptr; 2085 | if (ptr == end) 2086 | continue; 2087 | 2088 | if (*ptr == '\"') { 2089 | state = AttrValueDQ; 2090 | start = ptr + 1; 2091 | } else if (*ptr == '\'') { 2092 | state = AttrValueSQ; 2093 | start = ptr + 1; 2094 | } else { 2095 | state = AttrValueNQ; 2096 | start = ptr; 2097 | } 2098 | break; 2099 | 2100 | case AttrValueDQ: 2101 | while ((ptr != end) && (*ptr != '\"')) 2102 | ++ptr; 2103 | if (ptr == end) 2104 | continue; 2105 | 2106 | rule.value_ = start[0..ptr-start]; 2107 | state = PostAttrValue; 2108 | break; 2109 | 2110 | case AttrValueSQ: 2111 | while ((ptr != end) && (*ptr != '\'')) 2112 | ++ptr; 2113 | if (ptr == end) 2114 | continue; 2115 | 2116 | rule.value_ = start[0..ptr-start]; 2117 | state = PostAttrValue; 2118 | break; 2119 | 2120 | case AttrValueNQ: 2121 | while ((ptr != end) && !isWhite(*ptr) && (*ptr != ']')) 2122 | ++ptr; 2123 | if (ptr == end) 2124 | continue; 2125 | 2126 | rule.value_ = start[0..ptr-start]; 2127 | state = PostAttrValue; 2128 | continue; 2129 | 2130 | case PostAttrValue: 2131 | while ((ptr != end) && (*ptr != ']') && (*ptr != 'i')) 2132 | ++ptr; 2133 | if (ptr == end) 2134 | continue; 2135 | 2136 | if (*ptr == ']') { 2137 | state = PostIdentifier; 2138 | } else if (*ptr == 'i') { 2139 | rule.flags_ &= ~cast(int)Rule.Flags.CaseSensitive; 2140 | } 2141 | break; 2142 | 2143 | case Pseudo: 2144 | while ((ptr != end) && (isAlpha(*ptr) || (*ptr == '-'))) 2145 | ++ptr; 2146 | if (ptr == end) 2147 | continue; 2148 | 2149 | rule.pseudo_ = quickHashOf(start[0..ptr-start]); 2150 | rule.flags_ |= Rule.Flags.HasPseudo; 2151 | ++classes; 2152 | if (*ptr != '(') { 2153 | state = PostIdentifier; 2154 | continue; 2155 | } else { 2156 | state = PseudoArgs; 2157 | start = ptr + 1; 2158 | } 2159 | break; 2160 | 2161 | case PseudoArgs: 2162 | while ((ptr != end) && (*ptr != ')')) 2163 | ++ptr; 2164 | if (ptr == end) 2165 | continue; 2166 | 2167 | rule.pseudoArg_ = start[0..ptr-start]; 2168 | if (isNumeric(rule.pseudoArg_)) 2169 | rule.pseudoArgNum_ = to!uint(rule.pseudoArg_); 2170 | state = PostIdentifier; 2171 | break; 2172 | 2173 | case Relation: 2174 | while ((ptr != end) && isWhite(*ptr)) 2175 | ++ptr; 2176 | if (ptr == end) 2177 | continue; 2178 | 2179 | ++rules.length; 2180 | rule = &rules.back; 2181 | 2182 | state = Identifier; 2183 | switch (*ptr) { 2184 | case '>': 2185 | rule.relation_ = Rule.Relation.Child; 2186 | break; 2187 | case '+': 2188 | rule.relation_ = Rule.Relation.DirectAdjacent; 2189 | break; 2190 | case '~': 2191 | rule.relation_ = Rule.Relation.IndirectAdjacent; 2192 | break; 2193 | default: 2194 | rule.relation_ = Rule.Relation.Descendant; 2195 | continue; 2196 | } 2197 | break; 2198 | } 2199 | 2200 | ++ptr; 2201 | } 2202 | 2203 | rules.reverse(); 2204 | selector.rules_ = rules; 2205 | selector.specificity_ = (ids << 14) | (classes << 7) | (tags & 127); 2206 | 2207 | return selector; 2208 | } 2209 | 2210 | bool matches(const(Node) node) { 2211 | auto element = cast(Node)node; 2212 | if (rules_.empty) 2213 | return false; 2214 | 2215 | Rule.Relation relation = Rule.Relation.None; 2216 | foreach(ref rule; rules_) { 2217 | final switch (relation) with (Rule.Relation) { 2218 | case None: 2219 | if (!rule.matches(element)) 2220 | return false; 2221 | break; 2222 | case Descendant: 2223 | auto parent = element.parent_; 2224 | while (parent) { 2225 | if (rule.matches(parent)) { 2226 | element = parent; 2227 | break; 2228 | } 2229 | parent = parent.parent_; 2230 | } 2231 | if (!parent) 2232 | return false; 2233 | 2234 | break; 2235 | case Child: 2236 | auto parent = element.parent_; 2237 | if (!parent || !rule.matches(parent)) 2238 | return false; 2239 | element = parent; 2240 | break; 2241 | case DirectAdjacent: 2242 | auto adjacent = element.previousSibling; 2243 | if (!adjacent || !rule.matches(adjacent)) 2244 | return false; 2245 | element = adjacent; 2246 | break; 2247 | case IndirectAdjacent: 2248 | auto adjacent = element.previousSibling; 2249 | while (adjacent) { 2250 | if (rule.matches(adjacent)) { 2251 | element = adjacent; 2252 | break; 2253 | } 2254 | adjacent = adjacent.previousSibling; 2255 | } 2256 | if (!adjacent) 2257 | return false; 2258 | 2259 | break; 2260 | } 2261 | 2262 | relation = rule.relation; 2263 | } 2264 | 2265 | return true; 2266 | } 2267 | 2268 | @property size_t specificity() const { 2269 | return specificity_; 2270 | } 2271 | 2272 | private: 2273 | HTMLString source_; 2274 | Rule[] rules_; 2275 | size_t specificity_; 2276 | } 2277 | 2278 | 2279 | private size_t elementFlags(hash_t tagHash) { 2280 | size_t flags; 2281 | switch (tagHash) { 2282 | case tagHashOf("body"): 2283 | case tagHashOf("center"): 2284 | case tagHashOf("div"): 2285 | case tagHashOf("dl"): 2286 | case tagHashOf("form"): 2287 | case tagHashOf("head"): 2288 | case tagHashOf("html"): 2289 | case tagHashOf("noscript"): 2290 | case tagHashOf("ol"): 2291 | case tagHashOf("p"): 2292 | case tagHashOf("table"): 2293 | case tagHashOf("tbody"): 2294 | case tagHashOf("td"): 2295 | case tagHashOf("tfoot"): 2296 | case tagHashOf("th"): 2297 | case tagHashOf("thead"): 2298 | case tagHashOf("title"): 2299 | case tagHashOf("tr"): 2300 | case tagHashOf("ul"): 2301 | flags |= Node.Flags.BlockElement; 2302 | break; 2303 | case tagHashOf("br"): 2304 | case tagHashOf("hr"): 2305 | case tagHashOf("link"): 2306 | case tagHashOf("meta"): 2307 | flags |= Node.Flags.BlockElement; 2308 | flags |= Node.Flags.VoidElement; 2309 | break; 2310 | case tagHashOf("area"): 2311 | case tagHashOf("base"): 2312 | case tagHashOf("basefont"): 2313 | case tagHashOf("col"): 2314 | case tagHashOf("embed"): 2315 | case tagHashOf("img"): 2316 | case tagHashOf("input"): 2317 | case tagHashOf("isindex"): 2318 | case tagHashOf("param"): 2319 | case tagHashOf("source"): 2320 | case tagHashOf("track"): 2321 | case tagHashOf("wbr"): 2322 | flags |= Node.Flags.VoidElement; 2323 | break; 2324 | case tagHashOf("script"): 2325 | flags |= Node.Flags.BlockElement; 2326 | flags |= Node.Flags.ScriptElement; 2327 | break; 2328 | case tagHashOf("style"): 2329 | flags |= Node.Flags.BlockElement; 2330 | flags |= Node.Flags.StyleElement; 2331 | break; 2332 | default: 2333 | break; 2334 | } 2335 | return flags; 2336 | } 2337 | --------------------------------------------------------------------------------