UNPKG

10.9 kBJavaScriptView Raw
1
2
3/**
4 * @version 0.3.5
5 * @date 2015-02-02
6 * @stability 2 - Unstable
7 * @author Lauri Rooden <lauri@rooden.ee>
8 * @license MIT License
9 */
10
11
12// Void elements: http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
13var voidElements = {
14 AREA:1, BASE:1, BR:1, COL:1, EMBED:1, HR:1, IMG:1, INPUT:1,
15 KEYGEN:1, LINK:1, MENUITEM:1, META:1, PARAM:1, SOURCE:1, TRACK:1, WBR:1
16}
17, hasOwn = Object.prototype.hasOwnProperty
18, selectorCache = {}
19, selectorRe = /([.#:[])([-\w]+)(?:([~^$*|]?)=((["'\/])(?:\\?.)*?\4|[-\w]+)])?]?/g
20, lastSelectorRe = /(\s*[>+]?\s*)((["'\/])(?:\\?.)*?\2|[^\s+>])+$/
21, pseudoClasses = {
22 "empty": "!_.hasChildNodes()",
23 "first-child": "_.parentNode&&_.parentNode.firstChild==_",
24 "last-child" : "_.parentNode&&_.parentNode.lastChild==_",
25 "link": "_.nodeName=='A'&&_.getAttribute('href')"
26}
27
28
29function extend(obj, _super, extras) {
30 obj.prototype = Object.create(_super.prototype)
31 for (var key in extras) {
32 obj.prototype[key] = extras[key]
33 }
34 obj.prototype.constructor = obj
35}
36
37function StyleMap(style) {
38 var styleMap = this
39 if (style) style.split(/\s*;\s*/g).map(function(val) {
40 val = val.split(/\s*:\s*/)
41 if(val[1]) styleMap[val[0]] = val[1]
42 })
43}
44
45StyleMap.prototype.valueOf = function() {
46 var styleMap = this
47 return Object.keys(styleMap).map(function(key) {
48 return key + ": " + styleMap[key]
49 }).join("; ")
50}
51
52function Node(){}
53
54function getSibling(node, step) {
55 var silbings = node.parentNode && node.parentNode.childNodes
56 , index = silbings && silbings.indexOf(node)
57
58 return silbings && index > -1 && silbings[ index + step ] || null
59}
60
61Node.prototype = {
62 nodeName: null,
63 parentNode: null,
64 ownerDocument: null,
65 childNodes: null,
66 get nodeValue() {
67 return this.nodeType === 3 || this.nodeType === 8 ? this.data : null
68 },
69 set nodeValue(text) {
70 return this.nodeType === 3 || this.nodeType === 8 ? (this.data = text) : null
71 },
72 get textContent() {
73 return this.hasChildNodes() ? this.childNodes.map(function(child) {
74 return child[ child.nodeType == 3 ? "data" : "textContent" ]
75 }).join("") : this.nodeType === 3 ? this.data : ""
76 },
77 set textContent(text) {
78 if (this.nodeType === 3) return (this.data = text)
79 for (var node = this; node.firstChild;) node.removeChild(node.firstChild)
80 node.appendChild(node.ownerDocument.createTextNode(text))
81 },
82 get firstChild() {
83 return this.childNodes && this.childNodes[0] || null
84 },
85 get lastChild() {
86 return this.childNodes && this.childNodes[ this.childNodes.length - 1 ] || null
87 },
88 get previousSibling() {
89 return getSibling(this, -1)
90 },
91 get nextSibling() {
92 return getSibling(this, 1)
93 },
94 get innerHTML() {
95 return Node.prototype.toString.call(this)
96 },
97 get outerHTML() {
98 return this.toString()
99 },
100 get htmlFor() {
101 return this["for"]
102 },
103 set htmlFor(value) {
104 this["for"] = value
105 },
106 get className() {
107 return this["class"] || ""
108 },
109 set className(value) {
110 this["class"] = value
111 },
112 get style() {
113 return this.styleMap || (this.styleMap = new StyleMap())
114 },
115 set style(value) {
116 this.styleMap = new StyleMap(value)
117 },
118 hasChildNodes: function() {
119 return this.childNodes && this.childNodes.length > 0
120 },
121 appendChild: function(el) {
122 return this.insertBefore(el)
123 },
124 insertBefore: function(el, ref) {
125 var node = this
126 , childs = node.childNodes
127
128 if (el.nodeType == 11) {
129 while (el.firstChild) node.insertBefore(el.firstChild, ref)
130 } else {
131 if (el.parentNode) el.parentNode.removeChild(el)
132 el.parentNode = node
133
134 // If ref is null, insert el at the end of the list of children.
135 childs.splice(ref ? childs.indexOf(ref) : childs.length, 0, el)
136 }
137 return el
138 },
139 removeChild: function(el) {
140 var node = this
141 , index = node.childNodes.indexOf(el)
142 if (index == -1) throw new Error("NOT_FOUND_ERR")
143
144 node.childNodes.splice(index, 1)
145 el.parentNode = null
146 return el
147 },
148 replaceChild: function(el, ref) {
149 this.insertBefore(el, ref)
150 return this.removeChild(ref)
151 },
152 cloneNode: function(deep) {
153 var key
154 , node = this
155 , clone = new node.constructor(node.tagName || node.data)
156 clone.ownerDocument = node.ownerDocument
157
158 if (node.hasAttribute) {
159 for (key in node) if (node.hasAttribute(key)) clone[key] = node[key].valueOf()
160 }
161
162 if (deep && node.hasChildNodes()) {
163 node.childNodes.forEach(function(child) {
164 clone.appendChild(child.cloneNode(deep))
165 })
166 }
167 return clone
168 },
169 toString: function() {
170 return this.hasChildNodes() ? this.childNodes.reduce(function(memo, node) {
171 return memo + node
172 }, "") : ""
173 }
174}
175
176
177function DocumentFragment() {
178 this.childNodes = []
179}
180
181extend(DocumentFragment, Node, {
182 nodeType: 11,
183 nodeName: "#document-fragment"
184})
185
186function Attribute(node, name) {
187 this.name = name.toLowerCase()
188
189 Object.defineProperty(this, "value", {
190 get: function() {return node.getAttribute(name)},
191 set: function(val) {node.setAttribute(name, val)}
192 })
193}
194Attribute.prototype.toString = function() {
195 if (!this.value) return this.name
196 // jshint -W108
197 return this.name + '="' + this.value.replace(/&/g, "&amp;").replace(/"/g, "&quot;") + '"'
198}
199
200function findEl(node, sel, first) {
201 var el
202 , i = 0
203 , out = []
204 , els = node.getElementsByTagName("*")
205 , fn = selectorFn(sel.split(/\s*,\s*/).map(function(sel) {
206 return "_.matches('" + sel + "')"
207 }).join("||"))
208
209 for (; (el = els[i++]); ) if (fn(el)) {
210 if (first) return el
211 out.push(el)
212 }
213 return first ? null : out
214}
215
216function escapeAttributeName(name) {
217 name = name.toLowerCase()
218 if (name === "constructor" || name === "attributes") return name.toUpperCase()
219 return name
220}
221
222function selectorFnStr(sel) {
223 var rules = ["_"]
224 , tag = sel.replace(selectorRe, function(_, op, key, fn, val, quotation, len) {
225 if (quotation) val = val.slice(1, -1)
226 if (val) {
227 len = val.length
228 val = val.replace(/'/g, "\\'")
229 }
230 rules.push(
231 op == "." ? "(' '+_.className+' ').indexOf(' " + key + " ')>-1" :
232 op == "#" ? "_.id=='" + key + "'" :
233 op == ":" && pseudoClasses[key] ||
234 "(a=_.getAttribute('" + key + "'))" + (!fn && val ? "=='" + val + "'" : "")
235 )
236 if (fn) {
237 rules.push(
238 fn == "^" ? "a.slice(0," + len + ")=='" + val + "'" :
239 fn == "|" ? "a.split('-')[0]=='" + val + "'" :
240 fn == "$" ? "a.slice(-" + len + ")=='" + val + "'" :
241 fn == "~" ? "(' '+a+' ').indexOf(' " + val + " ')>-1" :
242 "a.indexOf('" + val + "')>-1" // fn == "*"
243 )
244 }
245 return ""
246 })
247
248 if (tag && tag != "*") rules.unshift("_.nodeName=='" + tag.toUpperCase() + "'")
249 return rules.join("&&")
250}
251
252function selectorFn(str) {
253 // jshint evil:true
254 return selectorCache[str] ||
255 (selectorCache[str] = Function("_,a", "return " + str))
256}
257
258function HTMLElement(tag) {
259 var element = this
260 element.nodeName = element.tagName = tag.toUpperCase()
261 element.localName = tag.toLowerCase()
262 element.childNodes = []
263}
264
265extend(HTMLElement, Node, {
266 matches: function(sel) {
267 var relation, from
268 , parentSel = sel.replace(lastSelectorRe, function(_, _rel, a, b, start) {
269 from = start + _rel.length
270 relation = _rel.trim()
271 return ""
272 })
273 , next = relation == "+" ? this.previousSibling : this.parentNode
274 , fn = selectorFn(selectorFnStr(sel.slice(from)))
275
276 if (!fn(this)) return false
277
278 if (parentSel) {
279 if (!relation) return !!(next && next.closest && next.closest(parentSel))
280 return next && next.matches && next.matches(parentSel) || false
281 }
282 return true
283 },
284 closest: function(sel) {
285 for (var el = this; el; el = el.parentNode) if (el.matches && el.matches(sel)) return el
286 return null
287 },
288 namespaceURI: "http://www.w3.org/1999/xhtml",
289 nodeType: 1,
290 localName: null,
291 tagName: null,
292 styleMap: null,
293 hasAttribute: function(name) {
294 name = escapeAttributeName(name)
295 return name == "style" && !!this.style.valueOf() || hasOwn.call(this, name)
296 },
297 getAttribute: function(name) {
298 name = escapeAttributeName(name)
299 return this.hasAttribute(name) ? "" + this[name] : null
300 },
301 setAttribute: function(name, value) {
302 this[escapeAttributeName(name)] = "" + value
303 },
304 removeAttribute: function(name) {
305 name = escapeAttributeName(name)
306 this[name] = ""
307 delete this[name]
308 },
309 getElementById: function(id) {
310 if (this.id == id) return this
311 for (var el, found, i = 0; !found && (el = this.childNodes[i++]);) {
312 if (el.nodeType == 1) found = el.getElementById(id)
313 }
314 return found || null
315 },
316 getElementsByTagName: function(tag) {
317 var el, els = [], next = this.firstChild
318 tag = tag === "*" ? 1 : tag.toUpperCase()
319 for (var i = 0, key = tag === 1 ? "nodeType" : "nodeName"; (el = next); ) {
320 if (el[key] === tag) els[i++] = el
321 next = el.firstChild || el.nextSibling
322 while (!next && ((el = el.parentNode) !== this)) next = el.nextSibling
323 }
324 return els
325 },
326 querySelector: function(sel) {
327 return findEl(this, sel, 1)
328 },
329 querySelectorAll: function(sel) {
330 return findEl(this, sel)
331 },
332 toString: function() {
333 var attrs = this.attributes.join(" ")
334 return "<" + this.localName + (attrs ? " " + attrs : "") + ">" +
335 (voidElements[this.tagName] ? "" : this.innerHTML + "</" + this.localName + ">")
336 }
337})
338
339Object.defineProperty(HTMLElement.prototype, "attributes", {
340 get: function() {
341 var key
342 , attrs = []
343 , element = this
344 for (key in element) if (key === escapeAttributeName(key) && element.hasAttribute(key))
345 attrs.push(new Attribute(element, escapeAttributeName(key)))
346 return attrs
347 }
348})
349
350function ElementNS(namespace, tag) {
351 var element = this
352 element.namespaceURI = namespace
353 element.nodeName = element.tagName = element.localName = tag
354 element.childNodes = []
355}
356
357ElementNS.prototype = HTMLElement.prototype
358
359function Text(data) {
360 this.data = data
361}
362
363extend(Text, Node, {
364 nodeType: 3,
365 nodeName: "#text",
366 toString: function() {
367 return ("" + this.data).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
368 }
369})
370
371function Comment(data) {
372 this.data = data
373}
374
375extend(Comment, Node, {
376 nodeType: 8,
377 nodeName: "#comment",
378 toString: function() {
379 return "<!--" + this.data + "-->"
380 }
381})
382
383function Document() {
384 this.childNodes = []
385 this.documentElement = this.createElement("html")
386 this.appendChild(this.documentElement)
387 this.body = this.createElement("body")
388 this.documentElement.appendChild(this.body)
389}
390
391function own(Element) {
392 return function($1, $2) {
393 var node = new Element($1, $2)
394 node.ownerDocument = this
395 return node
396 }
397}
398
399extend(Document, Node, {
400 nodeType: 9,
401 nodeName: "#document",
402 createElement: own(HTMLElement),
403 createElementNS: own(ElementNS),
404 createTextNode: own(Text),
405 createComment: own(Comment),
406 createDocumentFragment: own(DocumentFragment),
407 getElementById: HTMLElement.prototype.getElementById,
408 getElementsByTagName: HTMLElement.prototype.getElementsByTagName,
409 querySelector: HTMLElement.prototype.querySelector,
410 querySelectorAll: HTMLElement.prototype.querySelectorAll
411})
412
413module.exports = {
414 document: new Document(),
415 Document: Document,
416 HTMLElement: HTMLElement
417}
418