OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 "use strict"; |
| 5 |
| 6 (function(exports) { |
| 7 |
| 8 var GREATER_THAN = 62; |
| 9 var LESS_THAN = 60; |
| 10 var EQUALS = 61; |
| 11 var SINGLE_QUOTE = 39; |
| 12 var DOUBLE_QUOTE = 34; |
| 13 var SPACE = 32; |
| 14 var NEW_LINE = 10; |
| 15 var CARRIAGE_RETURN = 13; |
| 16 var TAB = 9; |
| 17 var SLASH = 47; |
| 18 |
| 19 class DomParser { |
| 20 constructor(string, bindings) { |
| 21 this.string = String(string || ""); |
| 22 this.state = "parseText"; |
| 23 this.offset = 0; |
| 24 this.stack = [bindings.createDocumentFragment()]; |
| 25 this.bindings = bindings; |
| 26 // Object.preventExtensions(this); |
| 27 } |
| 28 |
| 29 parse() { |
| 30 while (this.hasMoreChars()) { |
| 31 // console.log({state: this.state}, this.string.substring(this.offset)); |
| 32 this[this.state](); |
| 33 } |
| 34 return this.stack[0]; |
| 35 } |
| 36 |
| 37 parseText() { |
| 38 var value = this.consumeStringUntil(LESS_THAN); |
| 39 if (value) { |
| 40 var text = this.bindings.createText(value); |
| 41 this.bindings.appendChild(this.currentNode(), text); |
| 42 } |
| 43 if (this.currentCharCode() === LESS_THAN) { |
| 44 this.offset++; |
| 45 this.state = "parseTagName"; |
| 46 } |
| 47 // console.log("parseText", value); |
| 48 } |
| 49 |
| 50 parseTagName() { |
| 51 var endTag = false; |
| 52 if (this.currentCharCode() === SLASH) { |
| 53 this.offset++; |
| 54 endTag = true; |
| 55 } |
| 56 var tagName = this.consumeTagIdent(); |
| 57 if (tagName && endTag) { |
| 58 // Implicitly close all mis-nested tags. |
| 59 while (this.currentNode().tagName != tagName && this.stack.length > 1) { |
| 60 this.stack.pop(); |
| 61 } |
| 62 if (this.currentNode().tagName === tagName) |
| 63 this.stack.pop(); |
| 64 this.skipWhitespace(); |
| 65 if (this.currentCharCode() === GREATER_THAN) |
| 66 this.offset++; |
| 67 this.state = "parseText"; |
| 68 } else if (tagName && !endTag) { |
| 69 var element = this.bindings.createElement(tagName); |
| 70 this.bindings.appendChild(this.currentNode(), element); |
| 71 if (this.currentCharCode() === SLASH) { |
| 72 this.offset++; |
| 73 if (this.currentCharCode() === GREATER_THAN) { |
| 74 this.offset++; |
| 75 endTag = true; |
| 76 } |
| 77 } |
| 78 if (!endTag) { |
| 79 this.stack.push(element); |
| 80 this.state = "parseAttribute"; |
| 81 } else { |
| 82 this.state = "parseText"; |
| 83 } |
| 84 } |
| 85 |
| 86 // console.log("parseTagName", tagName, {endTag: endTag}); |
| 87 } |
| 88 |
| 89 parseAttribute() { |
| 90 this.skipWhitespace(); |
| 91 var name = this.consumeAttributeName(); |
| 92 var value = ""; |
| 93 this.skipWhitespace(); |
| 94 var charCode = this.currentCharCode(); |
| 95 if (charCode === EQUALS) { |
| 96 this.offset++; |
| 97 this.skipWhitespace(); |
| 98 charCode = this.currentCharCode(); |
| 99 if (charCode === SINGLE_QUOTE || charCode === DOUBLE_QUOTE) { |
| 100 this.offset++; |
| 101 value = this.consumeStringUntil(charCode); |
| 102 this.offset++; |
| 103 } else { |
| 104 value = this.consumeTagIdent(); |
| 105 } |
| 106 } |
| 107 if (name) |
| 108 this.bindings.setAttribute(this.currentNode(), name, value || ""); |
| 109 this.skipWhitespace(); |
| 110 charCode = this.currentCharCode(); |
| 111 var endTag = false; |
| 112 if (charCode === SLASH) { |
| 113 this.offset++; |
| 114 endTag = true; |
| 115 } |
| 116 charCode = this.currentCharCode(); |
| 117 if (charCode === GREATER_THAN) { |
| 118 if (endTag) |
| 119 this.stack.pop(); |
| 120 this.offset++; |
| 121 this.state = "parseText"; |
| 122 } |
| 123 // console.log("parseAttribute", name, value); |
| 124 } |
| 125 |
| 126 consumeAttributeName() { |
| 127 var startOffset = this.offset; |
| 128 while (this.hasMoreChars() && !this.isWhitespaceChar()) { |
| 129 var charCode = this.currentCharCode(); |
| 130 if (charCode === EQUALS || charCode === GREATER_THAN || charCode === SLASH
) |
| 131 break; |
| 132 this.advance(); |
| 133 } |
| 134 if (startOffset < this.offset) |
| 135 return this.string.substring(this.offset, startOffset); |
| 136 return ""; |
| 137 } |
| 138 |
| 139 consumeTagIdent() { |
| 140 var startOffset = this.offset; |
| 141 while (this.hasMoreChars() && !this.isWhitespaceChar()) { |
| 142 var charCode = this.currentCharCode(); |
| 143 if (charCode === GREATER_THAN || charCode === SLASH) |
| 144 break; |
| 145 this.advance(); |
| 146 } |
| 147 if (startOffset < this.offset) |
| 148 return this.string.substring(this.offset, startOffset); |
| 149 return ""; |
| 150 } |
| 151 |
| 152 consumeStringUntil(stopChar) { |
| 153 var startOffset = this.offset; |
| 154 while (this.hasMoreChars()) { |
| 155 var charCode = this.currentCharCode(); |
| 156 if (charCode === stopChar) |
| 157 break; |
| 158 this.advance(); |
| 159 } |
| 160 if (startOffset < this.offset) |
| 161 return this.string.substring(this.offset, startOffset); |
| 162 return ""; |
| 163 } |
| 164 |
| 165 skipWhitespace() { |
| 166 while (this.hasMoreChars() && this.isWhitespaceChar()) |
| 167 this.advance(); |
| 168 } |
| 169 |
| 170 isWhitespaceChar() { |
| 171 var charCode = this.currentCharCode(); |
| 172 return charCode === SPACE || |
| 173 charCode === NEW_LINE || |
| 174 charCode === CARRIAGE_RETURN || |
| 175 charCode === TAB; |
| 176 } |
| 177 |
| 178 currentCharCode() { |
| 179 return this.string.charCodeAt(this.offset); |
| 180 } |
| 181 |
| 182 hasMoreChars() { |
| 183 return this.offset < this.string.length; |
| 184 } |
| 185 |
| 186 advance() { |
| 187 this.offset++; |
| 188 } |
| 189 |
| 190 currentNode() { |
| 191 return this.stack[this.stack.length - 1]; |
| 192 } |
| 193 } |
| 194 |
| 195 // Object.preventExtensions(DomParser.prototype); |
| 196 |
| 197 exports.DomParser = DomParser; |
| 198 |
| 199 })(this); |
OLD | NEW |