Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80684 views
1
/*
2
ServerJS Javascript DOM Level 1
3
*/
4
var inheritFrom = require("../utils").inheritFrom;
5
6
// utility functions
7
var attachId = function(id,elm,doc) {
8
if (id && elm && doc) {
9
if (!doc._ids[id]) {
10
doc._ids[id] = [];
11
}
12
doc._ids[id].push(elm);
13
}
14
};
15
var detachId = function(id,elm,doc) {
16
var elms, i;
17
if (id && elm && doc) {
18
if (doc._ids && doc._ids[id]) {
19
elms = doc._ids[id];
20
for (i=0;i<elms.length;i++) {
21
if (elms[i] === elm) {
22
elms.splice(i,1);
23
i--;
24
}
25
}
26
if (elms.length === 0) {
27
delete doc._ids[id];
28
}
29
}
30
}
31
};
32
33
var core = {
34
35
mapper: function(parent, filter, recursive) {
36
return function() {
37
return core.mapDOMNodes(parent, recursive !== false, filter);
38
};
39
},
40
41
// Returns Array
42
mapDOMNodes : function(parent, recursive, callback) {
43
function visit(parent, result) {
44
return Array.prototype.reduce.call(parent.childNodes, reducer, result);
45
}
46
47
function reducer(array, child) {
48
if (callback(child)) {
49
array.push(child);
50
}
51
if (recursive && child._childNodes) {
52
visit(child, array);
53
}
54
return array;
55
}
56
57
return visit(parent, []);
58
},
59
60
visitTree: function(root, callback) {
61
var cur = root; // TODO: Unroll this.
62
63
function visit(el) {
64
if (el) {
65
callback(el);
66
if (el._childNodes) {
67
var i = 0,
68
children = el._childNodes,
69
l = children.length;
70
71
for (i; i<l; i++) {
72
visit(children[i]);
73
}
74
}
75
}
76
}
77
visit(root);
78
},
79
80
markTreeReadonly: function(node) {
81
function markLevel(el) {
82
el._readonly = true;
83
// also mark attributes and their children read-only
84
if (el.attributes) {
85
var attributes = el.attributes, l = attributes.length, i=0;
86
attributes._readonly = true;
87
88
for (i; i<l; i++) {
89
core.visitTree(attributes[i], markLevel);
90
}
91
}
92
}
93
94
core.visitTree(node, markLevel);
95
}
96
};
97
98
// ExceptionCode
99
var INDEX_SIZE_ERR = core.INDEX_SIZE_ERR = 1,
100
DOMSTRING_SIZE_ERR = core.DOMSTRING_SIZE_ERR = 2,
101
HIERARCHY_REQUEST_ERR = core.HIERARCHY_REQUEST_ERR = 3,
102
WRONG_DOCUMENT_ERR = core.WRONG_DOCUMENT_ERR = 4,
103
INVALID_CHARACTER_ERR = core.INVALID_CHARACTER_ERR = 5,
104
NO_DATA_ALLOWED_ERR = core.NO_DATA_ALLOWED_ERR = 6,
105
NO_MODIFICATION_ALLOWED_ERR = core.NO_MODIFICATION_ALLOWED_ERR = 7,
106
NOT_FOUND_ERR = core.NOT_FOUND_ERR = 8,
107
NOT_SUPPORTED_ERR = core.NOT_SUPPORTED_ERR = 9,
108
INUSE_ATTRIBUTE_ERR = core.INUSE_ATTRIBUTE_ERR = 10,
109
110
// Node Types
111
ELEMENT_NODE = 1,
112
ATTRIBUTE_NODE = 2,
113
TEXT_NODE = 3,
114
CDATA_SECTION_NODE = 4,
115
ENTITY_REFERENCE_NODE = 5,
116
ENTITY_NODE = 6,
117
PROCESSING_INSTRUCTION_NODE = 7,
118
COMMENT_NODE = 8,
119
DOCUMENT_NODE = 9,
120
DOCUMENT_TYPE_NODE = 10,
121
DOCUMENT_FRAGMENT_NODE = 11,
122
NOTATION_NODE = 12;
123
124
var messages = core.exceptionMessages = { };
125
messages[INDEX_SIZE_ERR] = "Index size error";
126
messages[DOMSTRING_SIZE_ERR] = "DOMString size error";
127
messages[HIERARCHY_REQUEST_ERR] = "Hierarchy request error";
128
messages[WRONG_DOCUMENT_ERR] = "Wrong document";
129
messages[INVALID_CHARACTER_ERR] = "Invalid character";
130
messages[NO_DATA_ALLOWED_ERR] = "No data allowed";
131
messages[NO_MODIFICATION_ALLOWED_ERR] = "No modification allowed";
132
messages[NOT_FOUND_ERR] = "Not found";
133
messages[NOT_SUPPORTED_ERR] = "Not supported";
134
messages[INUSE_ATTRIBUTE_ERR] = "Attribute in use";
135
136
core.DOMException = function(code, message) {
137
this.code = code;
138
Error.call(this, core.exceptionMessages[code]);
139
this.message = core.exceptionMessages[code];
140
if(message) this.message = this.message + ": " + message;
141
if(Error.captureStackTrace) Error.captureStackTrace(this, core.DOMException);
142
};
143
144
core.DOMException.INDEX_SIZE_ERR = INDEX_SIZE_ERR;
145
core.DOMException.DOMSTRING_SIZE_ERR = DOMSTRING_SIZE_ERR;
146
core.DOMException.HIERARCHY_REQUEST_ERR = HIERARCHY_REQUEST_ERR;
147
core.DOMException.WRONG_DOCUMENT_ERR = WRONG_DOCUMENT_ERR;
148
core.DOMException.INVALID_CHARACTER_ERR = INVALID_CHARACTER_ERR;
149
core.DOMException.NO_DATA_ALLOWED_ERR = NO_DATA_ALLOWED_ERR;
150
core.DOMException.NO_MODIFICATION_ALLOWED_ERR = NO_MODIFICATION_ALLOWED_ERR;
151
core.DOMException.NOT_FOUND_ERR = NOT_FOUND_ERR;
152
core.DOMException.NOT_SUPPORTED_ERR = NOT_SUPPORTED_ERR;
153
core.DOMException.INUSE_ATTRIBUTE_ERR = INUSE_ATTRIBUTE_ERR;
154
155
inheritFrom(Error, core.DOMException, {
156
INDEX_SIZE_ERR : INDEX_SIZE_ERR,
157
DOMSTRING_SIZE_ERR : DOMSTRING_SIZE_ERR,
158
HIERARCHY_REQUEST_ERR : HIERARCHY_REQUEST_ERR,
159
WRONG_DOCUMENT_ERR : WRONG_DOCUMENT_ERR,
160
INVALID_CHARACTER_ERR : INVALID_CHARACTER_ERR,
161
NO_DATA_ALLOWED_ERR : NO_DATA_ALLOWED_ERR,
162
NO_MODIFICATION_ALLOWED_ERR : NO_MODIFICATION_ALLOWED_ERR,
163
NOT_FOUND_ERR : NOT_FOUND_ERR,
164
NOT_SUPPORTED_ERR : NOT_SUPPORTED_ERR,
165
INUSE_ATTRIBUTE_ERR : INUSE_ATTRIBUTE_ERR
166
});
167
168
core.NodeList = function NodeList(element, query) {
169
if (!query) {
170
// Non-live NodeList
171
if (Array.isArray(element)) {
172
Array.prototype.push.apply(this, element);
173
}
174
Object.defineProperties(this, {
175
_length: {value: element ? element.length : 0, writable:true}
176
});
177
} else {
178
Object.defineProperties(this, {
179
_element: {value: element},
180
_query: {value: query},
181
_snapshot: {writable: true},
182
_length: {value: 0, writable: true},
183
_version: {value: -1, writable: true}
184
});
185
this._update();
186
}
187
};
188
189
function lengthFromProperties(arrayLike) {
190
var max = -1;
191
for (var i in arrayLike) {
192
var asNumber = +i;
193
if (!isNaN(asNumber) && asNumber > max) {
194
max = asNumber;
195
}
196
}
197
return max + 1;
198
}
199
core.NodeList.prototype = {
200
_update: function() {
201
var i;
202
203
if (!this._element) {
204
this._length = lengthFromProperties(this);
205
} else {
206
if (this._version < this._element._version) {
207
var nodes = this._snapshot = this._query();
208
this._resetTo(nodes);
209
this._version = this._element._version;
210
}
211
}
212
},
213
_resetTo: function(array) {
214
var startingLength = lengthFromProperties(this);
215
for (var i = 0; i < startingLength; ++i) {
216
delete this[i];
217
}
218
219
for (var j = 0; j < array.length; ++j) {
220
this[j] = array[j];
221
}
222
this._length = array.length;
223
},
224
_toArray: function() {
225
if (this._element) {
226
this._update();
227
return this._snapshot;
228
}
229
230
return Array.prototype.slice.call(this);
231
},
232
get length() {
233
this._update();
234
return this._length || 0;
235
},
236
item: function(index) {
237
this._update();
238
return this[index] || null;
239
},
240
toString: function() {
241
return '[ jsdom NodeList ]: contains ' + this.length + ' items';
242
}
243
};
244
Object.defineProperty(core.NodeList.prototype, 'constructor', {
245
value: core.NodeList,
246
writable: true,
247
configurable: true
248
});
249
250
core.DOMImplementation = function DOMImplementation(document, /* Object */ features) {
251
this._ownerDocument = document;
252
this._features = {};
253
254
if (features) {
255
for (var feature in features) {
256
if (features.hasOwnProperty(feature)) {
257
this.addFeature(feature.toLowerCase(), features[feature]);
258
}
259
}
260
}
261
};
262
263
core.DOMImplementation.prototype = {
264
get ownerDocument() { return this._ownerDocument },
265
removeFeature : function(feature, version) {
266
feature = feature.toLowerCase();
267
if (this._features[feature]) {
268
if (version) {
269
var j = 0,
270
versions = this._features[feature],
271
l = versions.length;
272
273
for (j; j<l; j++) {
274
if (versions[j] === version) {
275
versions.splice(j,1);
276
return;
277
}
278
}
279
} else {
280
delete this._features[feature];
281
}
282
}
283
},
284
285
addFeature: function(feature, version) {
286
feature = feature.toLowerCase();
287
288
if (version) {
289
290
if (!this._features[feature]) {
291
this._features[feature] = [];
292
}
293
294
if (version instanceof Array) {
295
Array.prototype.push.apply(this._features[feature], version);
296
} else {
297
this._features[feature].push(version);
298
}
299
}
300
},
301
302
hasFeature: function(/* string */ feature, /* string */ version) {
303
feature = (feature) ? feature.toLowerCase() : '';
304
var versions = (this._features[feature]) ?
305
this._features[feature] :
306
false;
307
308
if (!version && versions.length && versions.length > 0) {
309
return true;
310
} else if (typeof versions === 'string') {
311
return versions === version;
312
} else if (versions.indexOf && versions.length > 0) {
313
for (var i = 0; i < versions.length; i++) {
314
var found = versions[i] instanceof RegExp ?
315
versions[i].test(version) :
316
versions[i] === version;
317
if (found) { return true; }
318
}
319
return false;
320
} else {
321
return false;
322
}
323
}
324
};
325
326
327
var attrCopy = function(src, dest, fn) {
328
if (src.attributes) {
329
var attrs = src.attributes, i, l = attrs.length, attr, copied;
330
for (i=0;i<l;i++) {
331
attr = attrs[i];
332
// skip over default attributes
333
if (!attr.specified) {
334
continue;
335
}
336
// TODO: consider duplicating this code and moving it into level2/core
337
if (attr.namespaceURI) {
338
dest.setAttributeNS(attr.namespaceURI,
339
attr.nodeName,
340
attr.nodeValue);
341
var localName = attr.nodeName.split(':').pop();
342
copied = dest.getAttributeNodeNS(attr.namespaceURI, localName);
343
} else {
344
dest.setAttribute(attr.nodeName, attr.nodeValue);
345
copied = dest.getAttributeNode(attr.nodeName);
346
}
347
if (typeof fn == "function") {
348
fn(attr, copied);
349
}
350
351
}
352
}
353
return dest;
354
};
355
356
core.Node = function Node(ownerDocument) {
357
this._childNodes = new core.NodeList();
358
this._ownerDocument = ownerDocument;
359
this._attributes = new AttributeList(ownerDocument, this);
360
this._nodeName = null;
361
this._childrenList = null;
362
this._version = 0;
363
this._nodeValue = null;
364
this._parentNode = null;
365
this._nodeName = null;
366
this._readonly = false;
367
};
368
369
core.Node.ELEMENT_NODE = ELEMENT_NODE;
370
core.Node.ATTRIBUTE_NODE = ATTRIBUTE_NODE;
371
core.Node.TEXT_NODE = TEXT_NODE;
372
core.Node.CDATA_SECTION_NODE = CDATA_SECTION_NODE;
373
core.Node.ENTITY_REFERENCE_NODE = ENTITY_REFERENCE_NODE;
374
core.Node.ENTITY_NODE = ENTITY_NODE;
375
core.Node.PROCESSING_INSTRUCTION_NODE = PROCESSING_INSTRUCTION_NODE;
376
core.Node.COMMENT_NODE = COMMENT_NODE;
377
core.Node.DOCUMENT_NODE = DOCUMENT_NODE;
378
core.Node.DOCUMENT_TYPE_NODE = DOCUMENT_TYPE_NODE;
379
core.Node.DOCUMENT_FRAGMENT_NODE = DOCUMENT_FRAGMENT_NODE;
380
core.Node.NOTATION_NODE = NOTATION_NODE;
381
382
core.Node.prototype = {
383
ELEMENT_NODE : ELEMENT_NODE,
384
ATTRIBUTE_NODE : ATTRIBUTE_NODE,
385
TEXT_NODE : TEXT_NODE,
386
CDATA_SECTION_NODE : CDATA_SECTION_NODE,
387
ENTITY_REFERENCE_NODE : ENTITY_REFERENCE_NODE,
388
ENTITY_NODE : ENTITY_NODE,
389
PROCESSING_INSTRUCTION_NODE : PROCESSING_INSTRUCTION_NODE,
390
COMMENT_NODE : COMMENT_NODE,
391
DOCUMENT_NODE : DOCUMENT_NODE,
392
DOCUMENT_TYPE_NODE : DOCUMENT_TYPE_NODE,
393
DOCUMENT_FRAGMENT_NODE : DOCUMENT_FRAGMENT_NODE,
394
NOTATION_NODE : NOTATION_NODE,
395
396
get children() {
397
if (!this._childrenList) {
398
var self = this;
399
this._childrenList = new core.NodeList(this, function() {
400
return Array.prototype.filter.call(self._childNodes, function(node) {
401
return node.tagName;
402
});
403
});
404
}
405
return this._childrenList;
406
},
407
get nodeValue() {
408
return this._nodeValue;
409
},
410
set nodeValue(value) {
411
// readonly
412
if (this._readonly === true) {
413
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR, 'Attempting to modify a read-only node');
414
}
415
416
this._nodeValue = value;
417
},
418
get parentNode() { return this._parentNode;},
419
420
get nodeName() {
421
var name = this._nodeName || this._tagName;
422
if (this.nodeType === ELEMENT_NODE &&
423
this._ownerDocument &&
424
this._ownerDocument._doctype &&
425
this._ownerDocument._doctype.name.toLowerCase().indexOf("html") !== -1)
426
{
427
return name.toUpperCase();
428
}
429
return name;
430
},
431
set nodeName(unused) { throw new core.DOMException();},
432
get attributes() { return this._attributes;},
433
get firstChild() {
434
return this._childNodes.length > 0 ? this._childNodes[0] : null;
435
},
436
set firstChild(unused) { throw new core.DOMException();},
437
get ownerDocument() { return this._ownerDocument;},
438
get readonly() { return this._readonly;},
439
440
get lastChild() {
441
var len = this._childNodes.length;
442
return len > 0 ? this._childNodes[len -1] : null;
443
},
444
set lastChild(unused) { throw new core.DOMException();},
445
446
get childNodes() {
447
return this._childNodes;
448
},
449
set childNodes(unused) { throw new core.DOMException();},
450
451
_indexOf: function(/*Node*/ child) {
452
if (!this._childNodes ||
453
!this._childNodes.length) {
454
return -1;
455
}
456
457
var currentNode, index = 0, children = this._childNodes;
458
459
while ((currentNode = children[index])) {
460
if (currentNode == child) {
461
break;
462
}
463
index++;
464
}
465
466
if (currentNode == child) {
467
return index;
468
}
469
return -1;
470
},
471
472
get nextSibling() {
473
// find this node's index in the parentNode, add one and call it a day
474
if (!this._parentNode || !this._parentNode._indexOf) {
475
return null;
476
}
477
478
var index = this._parentNode._indexOf(this);
479
480
if (index == -1 || index+1 >= this._parentNode._childNodes.length) {
481
return null;
482
}
483
484
return this._parentNode._childNodes[index+1] || null;
485
},
486
set nextSibling(unused) { throw new core.DOMException();},
487
488
get previousSibling() {
489
if (!this._parentNode || !this._parentNode._indexOf) {
490
return null;
491
}
492
493
var index = this._parentNode._indexOf(this);
494
495
if (index == -1 || index-1 < 0) {
496
return null;
497
}
498
499
return this._parentNode._childNodes[index-1] || null;
500
},
501
set previousSibling(unused) { throw new core.DOMException();},
502
503
/* returns Node */
504
insertBefore : function(/* Node */ newChild, /* Node*/ refChild) {
505
if (this._readonly === true) {
506
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR, 'Attempting to modify a read-only node');
507
}
508
509
// Adopt unowned children, for weird nodes like DocumentType
510
if (!newChild._ownerDocument) newChild._ownerDocument = this._ownerDocument;
511
512
// TODO - if (!newChild) then?
513
if (newChild._ownerDocument !== this._ownerDocument) {
514
throw new core.DOMException(WRONG_DOCUMENT_ERR);
515
}
516
517
if (newChild.nodeType && newChild.nodeType === ATTRIBUTE_NODE) {
518
throw new core.DOMException(HIERARCHY_REQUEST_ERR);
519
}
520
521
// search for parents matching the newChild
522
var current = this;
523
do {
524
if (current === newChild) {
525
throw new core.DOMException(HIERARCHY_REQUEST_ERR);
526
}
527
} while((current = current._parentNode));
528
529
// fragments are merged into the element
530
if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) {
531
var tmpNode, i = newChild._childNodes.length;
532
while (i-- > 0) {
533
tmpNode = newChild.removeChild(newChild.firstChild);
534
this.insertBefore(tmpNode, refChild);
535
}
536
} else if (newChild === refChild) {
537
return newChild;
538
} else {
539
// if the newChild is already in the tree elsewhere, remove it first
540
if (newChild._parentNode) {
541
newChild._parentNode.removeChild(newChild);
542
}
543
544
if (refChild == null) {
545
var refChildIndex = this._childNodes.length;
546
} else {
547
var refChildIndex = this._indexOf(refChild);
548
if (refChildIndex == -1) {
549
throw new core.DOMException(NOT_FOUND_ERR);
550
}
551
}
552
553
Array.prototype.splice.call(this._childNodes, refChildIndex, 0, newChild);
554
555
newChild._parentNode = this;
556
if (this._attached && newChild._attach) {
557
newChild._attach();
558
}
559
560
this._modified();
561
}
562
563
return newChild;
564
}, // raises(DOMException);
565
566
_modified: function() {
567
this._version++;
568
if (this._ownerDocument) {
569
this._ownerDocument._version++;
570
}
571
572
if (this._childrenList) {
573
this._childrenList._update();
574
}
575
},
576
577
_attrModified: function(name, value, oldValue) {
578
if (name == 'id' && this._attached) {
579
var doc = this._ownerDocument;
580
detachId(oldValue,this,doc);
581
attachId(value,this,doc);
582
}
583
584
// Check for inline event handlers.
585
// We can't set these like other attributes then look it up in
586
// dispatchEvent() because that would create 2 'traditional' event handlers
587
// in the case where there's an inline event handler attribute, plus one
588
// set using element.on* in a script.
589
//
590
// @see http://www.w3.org/TR/2011/WD-html5-20110405/webappapis.html#event-handler-content-attributes
591
if ((name.length > 2) && (name[0] == 'o') && (name[1] == 'n')) {
592
if (value) {
593
var self = this;
594
// Check whether we're the window. This can happen because inline
595
// handlers on the body are proxied to the window.
596
var w = (typeof self.run !== 'undefined') ? self : self._ownerDocument.parentWindow;
597
self[name] = function (event) {
598
// The handler code probably refers to functions declared in the
599
// window context, so we need to call run().
600
601
// Use awesome hacks to get the correct `this` context for the
602
// inline event handler. This would only be necessary if we're an
603
// element, but for the sake of simplicity we also do it on window.
604
605
// Also set event variable and support `return false`.
606
w.__tempContextForInlineEventHandler = self;
607
w.__tempEvent = event;
608
w.run("if ((function (event) {" + value + "}).call(" +
609
"window.__tempContextForInlineEventHandler, window.__tempEvent) === false) {" +
610
"window.__tempEvent.preventDefault()}");
611
delete w.__tempContextForInlineEventHandler;
612
delete w.__tempEvent;
613
};
614
} else {
615
this[name] = null;
616
}
617
}
618
},
619
620
/* returns Node */
621
replaceChild : function(/* Node */ newChild, /* Node */ oldChild){
622
this.insertBefore(newChild, oldChild);
623
return this.removeChild(oldChild);
624
}, //raises(DOMException);
625
626
/* returns void */
627
_attach : function() {
628
this._attached = true;
629
if (this.id) {
630
attachId(this.id,this,this._ownerDocument);
631
}
632
for (var i=0,len=this._childNodes.length;i<len;i++) {
633
if (this._childNodes[i]._attach) {
634
this._childNodes[i]._attach();
635
}
636
}
637
},
638
/* returns void */
639
_detach : function() {
640
var i, elms;
641
this._attached = false;
642
if (this.id) {
643
detachId(this.id,this,this._ownerDocument);
644
}
645
for (var i=0,len=this._childNodes.length;i<len;i++) {
646
this._childNodes[i]._detach();
647
}
648
},
649
650
/* returns Node */
651
removeChild : function(/* Node */ oldChild){
652
if (this._readonly === true) {
653
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
654
}
655
656
// TODO - if (!oldChild) then?
657
var oldChildIndex = this._indexOf(oldChild);
658
if (oldChildIndex == -1) {
659
throw new core.DOMException(NOT_FOUND_ERR);
660
}
661
662
Array.prototype.splice.call(this._childNodes, oldChildIndex, 1);
663
oldChild._parentNode = null;
664
this._modified();
665
oldChild._detach();
666
return oldChild;
667
}, // raises(DOMException);
668
669
/* returns Node */
670
appendChild : function(/* Node */ newChild) {
671
return this.insertBefore(newChild, null);
672
}, // raises(DOMException);
673
674
/* returns boolean */
675
hasChildNodes : function() {
676
return this._childNodes.length > 0;
677
},
678
679
/* returns Node */
680
cloneNode : function(/* bool */ deep, fn) {
681
682
var object = null;
683
switch (this.nodeType) {
684
685
case this.ELEMENT_NODE:
686
object = attrCopy(this,this._ownerDocument.createElement(this.tagName), fn);
687
break;
688
689
case this.TEXT_NODE:
690
object = attrCopy(this,this._ownerDocument.createTextNode(this.tagName));
691
object.nodeValue = this.nodeValue;
692
break;
693
case this.CDATA_SECTION_NODE:
694
object = this._ownerDocument.createCDATASection(this.tagName);
695
object.nodeValue = this.nodeValue;
696
break;
697
case this.ENTITY_REFERENCE_NODE:
698
var name = (this._entity) ? this._entity.name : this._entityName,
699
ref = this._ownerDocument.createEntityReference(name);
700
701
object = attrCopy(this, ref);
702
object.nodeValue = this.nodeValue;
703
break;
704
case this.ATTRIBUTE_NODE:
705
object = this._ownerDocument.createAttribute(this.name);
706
break;
707
case this.ENTITY_NODE:
708
var entity = this._ownerDocument.createEntityNode(this.name);
709
object = attrCopy(this, entity);
710
object.nodeValue = this.nodeValue;
711
object._publicId = this._publicId;
712
object._systemId = this._systemId;
713
object._notationName = this.notationName;
714
break;
715
case this.PROCESSING_INSTRUCTION_NODE:
716
var pi = this._ownerDocument.createProcessingInstruction(this._target,
717
this._data);
718
object = attrCopy(this, pi);
719
object.nodeValue = this.nodeValue;
720
break;
721
case this.COMMENT_NODE:
722
object = this._ownerDocument.createComment(this.tagName);
723
object.nodeValue = this.nodeValue;
724
break;
725
case this.DOCUMENT_NODE:
726
object = attrCopy(this, new core.Document());
727
// TODO: clone the doctype/entities/notations/etc?
728
break;
729
case this.DOCUMENT_TYPE_NODE:
730
object = attrCopy(this, new core.DocumentType());
731
object.nodeValue = this.nodeValue;
732
break;
733
case this.DOCUMENT_FRAGMENT_NODE:
734
object = this._ownerDocument.createDocumentFragment();
735
break;
736
case this.NOTATION_NODE:
737
object = this._ownerDocument.createNotationNode(this._name,
738
this._publicId,
739
this._systemId);
740
object = attrCopy(this,object);
741
object.nodeValue = this.nodeValue;
742
break;
743
default:
744
throw new core.DOMException(NOT_FOUND_ERR);
745
break;
746
}
747
748
if (typeof fn === "function") {
749
fn(this, object);
750
}
751
752
if (deep || this.nodeType === ATTRIBUTE_NODE) {
753
var clone = null;
754
for (var i=0,len=this._childNodes.length;i<len;i++)
755
{
756
clone = this._childNodes[i].cloneNode(true);
757
if (clone.nodeType === ATTRIBUTE_NODE) {
758
object.setAttributeNode(clone);
759
} else {
760
var readonly = object._readonly;
761
object._readonly = false;
762
object.appendChild(clone);
763
object._readonly = readonly;
764
}
765
}
766
}
767
768
return object;
769
},
770
771
/* returns void */
772
normalize: function() {
773
var prevChild, child, attr,i;
774
775
if (this._attributes && this._attributes.length) {
776
for (i=0;i<this._attributes.length;i++)
777
{
778
if (this._attributes[i]) {
779
attr = this._attributes[i].normalize();
780
}
781
}
782
}
783
784
for (i=0;i<this._childNodes.length;i++)
785
{
786
child = this._childNodes[i];
787
788
if (child.normalize) {
789
child.normalize();
790
}
791
792
// Level2/core clean off empty nodes
793
if (child.nodeValue === "") {
794
this.removeChild(child);
795
i--;
796
continue;
797
}
798
799
if (i>0) {
800
prevChild = this._childNodes[i-1];
801
802
if (child.nodeType === TEXT_NODE &&
803
prevChild.nodeType === TEXT_NODE)
804
{
805
806
// remove the child and decrement i
807
prevChild.appendData(child.nodeValue);
808
809
this.removeChild(child);
810
i--;
811
}
812
}
813
}
814
},
815
toString: function() {
816
var id = '';
817
if (this.id) {
818
id = '#' + this.id;
819
}
820
if (this.className) {
821
var classes = this.className.split(/\s+/);
822
for (var i = 0, len = classes.length; i < len; i++) {
823
id += '.' + classes[i];
824
}
825
}
826
return '[ ' + this.tagName + id + ' ]';
827
},
828
raise: function(type, message, data) {
829
var text = type + ": " + message;
830
831
if (data) {
832
if (data.exception) {
833
text = data.exception.stack;
834
} else {
835
text += ' - More:\n' + data;
836
}
837
}
838
839
if (type === "error") {
840
if (!this.errors) {
841
this.errors = [];
842
}
843
// TODO: consider using actual `Error` objects or `DOMException`s even..
844
var err = {
845
type : type,
846
message : message || "No message",
847
data : data || null
848
};
849
850
this.errors.push(err);
851
852
if (this._ownerDocument &&
853
this._ownerDocument.raise &&
854
this !== this._ownerDocument)
855
{
856
this._ownerDocument.raise(type, message, data);
857
}
858
}
859
}
860
};
861
862
863
core.NamedNodeMap = function NamedNodeMap(document) {
864
this._nodes = Object.create(null);
865
this._nsStore = {};
866
this.length = 0;
867
this._ownerDocument = document;
868
this._readonly = false;
869
};
870
core.NamedNodeMap.prototype = {
871
get readonly() { return this._readonly;},
872
get ownerDocument() { this._ownerDocument;},
873
874
exists : function(name) {
875
return (this._nodes[name] || this._nodes[name] === null) ? true : false;
876
},
877
878
/* returns Node */
879
getNamedItem: function(/* string */ name) {
880
return this._nodes[name] || null;
881
},
882
883
/* returns Node */
884
setNamedItem: function(/* Node */ arg) {
885
886
// readonly
887
if (this._readonly === true) {
888
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
889
}
890
891
// arg is from a different document
892
if (arg && arg._ownerDocument !== this._ownerDocument) {
893
throw new core.DOMException(WRONG_DOCUMENT_ERR);
894
}
895
896
// if this argument is already in use..
897
if (arg && arg._ownerElement) {
898
throw new core.DOMException(INUSE_ATTRIBUTE_ERR);
899
}
900
901
var name = arg.name || arg.tagName;
902
var ret = this._nodes[name];
903
if (!ret) {
904
this.length++;
905
ret = null;
906
}
907
arg._specified = true;
908
this._nodes[name] = arg;
909
910
// Avoid overwriting prototype methods etc.:
911
if (this.hasOwnProperty(name) || !(name in this)) {
912
this[name] = arg;
913
}
914
return ret;
915
}, // raises: function(DOMException) {},
916
917
/* returns Node */
918
removeNamedItem: function(/* string */ name) {
919
920
// readonly
921
if (this._readonly === true) {
922
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
923
}
924
925
if (!this._nodes[name]) {
926
throw new core.DOMException(NOT_FOUND_ERR);
927
}
928
929
var prev = this._nodes[name] || null;
930
delete this._nodes[name];
931
delete this[name];
932
933
this.length--;
934
return prev;
935
}, // raises: function(DOMException) {},
936
937
/* returns Node */
938
item: function(/* int */ index) {
939
var current = 0;
940
for (var member in this._nodes) {
941
if (current === index && this._nodes[member]) {
942
return this._nodes[member];
943
}
944
current++;
945
}
946
return null;
947
}
948
};
949
950
//
951
// For historical reasons, AttributeList objects must allow accessing
952
// attributes as if the object were an associative array. For
953
// instance, if `attributes` is an AttributeList object then
954
// `attributes.x` should evaluate to the attribute named `x` (which is
955
// not in any namespace). The AttributeList class uses the dollar
956
// symbol ($) to reduce the possibility of a clash between its field
957
// names and possible attribute names. For instance, if the method
958
// currently named `$set` were instead named `set` then it would not
959
// be possible to access an attribute named `set` through
960
// `attributes.set`. The dollar symbol is not valid in attribute names
961
// so `$set` cannot clash.
962
//
963
// Some fields do not get the $ because:
964
//
965
// * They are part of the API (e.g. `setNamedItem`, `length`), so they
966
// must be visible under a specific name.
967
//
968
// * Jsdom's code which traverses the DOM tree expects regularly named
969
// fields (e.g. `_parentNode`).
970
//
971
function AttributeList(document, parentNode) {
972
this._ownerDocument = document;
973
this._parentNode = parentNode;
974
this._readonly = false;
975
this._$ns_to_attrs = Object.create(null);
976
this._$name_to_attrs = Object.create(null);
977
this.length = 0;
978
}
979
980
AttributeList.prototype = {
981
_$reserved: [], // Initialized later
982
983
984
//
985
// Code internal to jsdom and which manipulates an AttributeList
986
// object should use the following methods rather than the methods
987
// that provide the NamedNodeMap interface.
988
//
989
990
// This method *ignores* namespaces. This is *not* the same thing as
991
// requesting an attribute with a null namespace.
992
$getNoNS: function (name) {
993
var attrs = this._$name_to_attrs[name];
994
if (!attrs) {
995
return null;
996
}
997
998
return attrs[0] || null;
999
},
1000
1001
$getNode: function (namespace, localName) {
1002
var attrs = this._$ns_to_attrs[namespace];
1003
if (!attrs) {
1004
return null;
1005
}
1006
1007
var ret = attrs[localName];
1008
if (!ret) {
1009
return null;
1010
}
1011
1012
return ret;
1013
},
1014
1015
// This method *ignores* namespaces. This is *not* the same thing as
1016
// requesting an attribute with a null namespace.
1017
$setNoNS: function (name, value) {
1018
var attr = this.$getNoNS(name);
1019
if (!attr) {
1020
this.$set(name, value);
1021
return;
1022
}
1023
1024
var prev_val = attr.value;
1025
attr.value = value;
1026
attr._specified = true;
1027
1028
this._parentNode._attrModified(attr.name, attr.value, prev_val);
1029
this._parentNode._modified();
1030
},
1031
1032
$set: function (localName, value, name, prefix, namespace) {
1033
if (this._readonly) {
1034
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
1035
}
1036
1037
if (name === undefined) {
1038
name = localName;
1039
}
1040
1041
if (prefix === undefined) {
1042
prefix = null;
1043
}
1044
1045
if (namespace === undefined) {
1046
namespace = null;
1047
}
1048
1049
var prev_attr = this.$getNode(namespace, localName);
1050
var attr;
1051
1052
var prev_val = null;
1053
if (prev_attr) {
1054
prev_val = prev_attr.value;
1055
prev_attr._prefix = prefix;
1056
prev_attr.value = value;
1057
attr = prev_attr;
1058
attr._specified = true;
1059
1060
this._parentNode._attrModified(attr.name, attr.value, prev_val);
1061
this._parentNode._modified();
1062
}
1063
else {
1064
attr = this._ownerDocument.createAttribute(name);
1065
attr._ownerElement = this._parentNode;
1066
attr.value = value;
1067
attr._namespaceURI = namespace;
1068
attr._prefix = prefix;
1069
attr._localName = localName;
1070
attr._parentNode = this._parentNode;
1071
attr._created = true;
1072
this.$setNode(attr);
1073
// $setNode calls the parent node methods.
1074
}
1075
},
1076
1077
$setNode: function (attr) {
1078
if (this._readonly) {
1079
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
1080
}
1081
1082
if (attr.nodeType !== ATTRIBUTE_NODE) {
1083
throw new core.DOMException(HIERARCHY_REQUEST_ERR);
1084
}
1085
1086
if (attr._ownerDocument !== this._ownerDocument) {
1087
throw new core.DOMException(WRONG_DOCUMENT_ERR);
1088
}
1089
1090
if (attr._parentNode && attr._parentNode !== this._parentNode) {
1091
throw new core.DOMException(INUSE_ATTRIBUTE_ERR);
1092
}
1093
1094
var localName = attr._localName;
1095
var name = attr.name;
1096
var prefix = attr._prefix;
1097
var namespace = attr._namespaceURI;
1098
1099
if (name === undefined) {
1100
name = localName;
1101
}
1102
1103
if (prefix === undefined) {
1104
prefix = null;
1105
}
1106
1107
if (namespace === undefined) {
1108
namespace = null;
1109
}
1110
1111
var prev_attr = this.$getNode(namespace, localName);
1112
1113
var prev_val = null;
1114
if (prev_attr) {
1115
prev_val = prev_attr.value;
1116
// Remove the old attribute
1117
this._$onlyRemoveNode(prev_attr);
1118
}
1119
1120
attr._parentNode = this._parentNode;
1121
attr._ownerElement = this._parentNode;
1122
attr._specified = true;
1123
1124
var attrs = this._$ns_to_attrs[namespace];
1125
if (!attrs) {
1126
attrs = this._$ns_to_attrs[namespace] = Object.create(null);
1127
}
1128
attrs[localName] = attr;
1129
1130
attrs = this._$name_to_attrs[name];
1131
if (!attrs) {
1132
attrs = this._$name_to_attrs[name] = [attr];
1133
}
1134
else {
1135
attrs.push(attr);
1136
}
1137
1138
// Only attributes in the null namespace can be set this way.
1139
if (namespace === null) {
1140
// Make the node a field on this object but ONLY if it does not
1141
// clash with the reserved names.
1142
if (this._$reserved.indexOf(name) === -1) {
1143
this[name] = attr;
1144
}
1145
}
1146
1147
this[this.length] = attr;
1148
this.length++;
1149
1150
this._parentNode._attrModified(attr.name, attr.value, prev_val);
1151
this._parentNode._modified();
1152
1153
return prev_attr;
1154
},
1155
1156
// This method *ignores* namespaces. This is *not* the same thing as
1157
// requesting an attribute with a null namespace.
1158
$removeNoNS: function (name) {
1159
var attr = this.$getNoNS(name);
1160
return attr ? this.$removeNode(attr) : null;
1161
},
1162
1163
$remove: function (namespace, localName) {
1164
var attr = this.$getNode(namespace, localName);
1165
return attr ? this.$removeNode(attr) : null;
1166
},
1167
1168
/* Only removes the node, and does not add a default value. */
1169
_$onlyRemoveNode: function (attr) {
1170
var namespace = attr._namespaceURI;
1171
var localName = attr._localName;
1172
1173
var attrs = this._$ns_to_attrs[namespace];
1174
if (!attrs) {
1175
return null;
1176
}
1177
1178
var found_attr = attrs[localName];
1179
if (found_attr !== attr) {
1180
return null;
1181
}
1182
1183
if (this._readonly) {
1184
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
1185
}
1186
1187
attr._ownerElement = null;
1188
attr._parentNode = null;
1189
delete attrs[localName];
1190
1191
attrs = this._$name_to_attrs[attr.name];
1192
attrs.splice(attrs.indexOf(attr), 1);
1193
1194
var ix = Array.prototype.indexOf.call(this, attr);
1195
// Splice also modifies length.
1196
Array.prototype.splice.call(this, ix, 1);
1197
1198
if (this[attr.name] === attr) {
1199
delete this[attr.name];
1200
}
1201
1202
this._parentNode._attrModified(attr.name);
1203
this._parentNode._modified();
1204
1205
return attr;
1206
},
1207
1208
$removeNode: function (attr) {
1209
if (!this._$onlyRemoveNode(attr)) {
1210
return null;
1211
}
1212
1213
// set default value if available
1214
var doc = this._ownerDocument;
1215
if (doc && doc._doctype && doc._doctype.name.toLowerCase() !== "html") {
1216
var elem =
1217
doc._doctype._attributes.getNamedItem(this._parentNode.nodeName);
1218
1219
if (elem) {
1220
var defaultValue = elem.attributes.getNamedItemNS(attr._namespaceURI,
1221
attr._localName);
1222
1223
if (defaultValue) {
1224
this.$set(attr._localName, defaultValue.value, attr.name, attr._prefix,
1225
attr._namespaceURI);
1226
var new_attr = this.$getNode(attr._namespaceURI, attr._localName);
1227
new_attr._specified = false;
1228
}
1229
}
1230
}
1231
return attr;
1232
},
1233
1234
// Although http://dom.spec.whatwg.org/#concept-element-attribute
1235
// does not specify that the attributes field on an Element should
1236
// support NamedNodeMap, in practice browsers still support this
1237
// interface so we should support it for compatibility.
1238
1239
getNamedItem: function (name) {
1240
return this.getNamedItemNS(null, name);
1241
},
1242
removeNamedItem: function (name) {
1243
return this.removeNamedItemNS(null, name);
1244
},
1245
item: function (i) {
1246
return this[i];
1247
},
1248
getNamedItemNS: function (namespaceURI, localName) {
1249
if (namespaceURI === "") {
1250
namespaceURI = null;
1251
}
1252
1253
return this.$getNode(namespaceURI, localName);
1254
},
1255
removeNamedItemNS: function (namespaceURI, localName) {
1256
var ret = this.$remove(namespaceURI, localName);
1257
1258
if (ret === null) {
1259
throw new core.DOMException(NOT_FOUND_ERR);
1260
}
1261
1262
return ret;
1263
}
1264
};
1265
1266
// Alias these methods.
1267
AttributeList.prototype.setNamedItem = AttributeList.prototype.$setNode;
1268
AttributeList.prototype.setNamedItemNS = AttributeList.prototype.$setNode;
1269
1270
(function () {
1271
// Construct the list of reserved attribute names from a temporarily
1272
// created AttributeList and from the chain of prototypes. We need
1273
// this because JavaScript code running an a browser expects to be
1274
// able to do el.attributes.x to get the value of the attribute "x"
1275
// on an element. Unfortunately, JavaScript *currently* does not
1276
// allow us to elegantly provide such functionality without risking
1277
// a clash with the fields and methods set on the AttributeList
1278
// object. Hence we need a list of reserved field names.
1279
1280
var reserved = Object.keys(new AttributeList());
1281
var prototype = AttributeList.prototype;
1282
while (prototype) {
1283
reserved = reserved.concat(Object.getOwnPropertyNames(prototype));
1284
prototype = Object.getPrototypeOf(prototype);
1285
}
1286
AttributeList.prototype._$reserved = reserved;
1287
})();
1288
1289
core.AttributeList = AttributeList;
1290
1291
core.NotationNodeMap = function NotationNodeMap(document) {
1292
core.NamedNodeMap.call(this, document);
1293
this._readonly = false;
1294
for (var i=1;i<arguments.length;i++) {
1295
this.setNamedItem(arguments[i]);
1296
}
1297
this._readonly = true;
1298
};
1299
inheritFrom(core.NamedNodeMap, core.NotationNodeMap);
1300
1301
core.EntityNodeMap = function EntityNodeMap(document) {
1302
core.NamedNodeMap.call(this,document);
1303
this._readonly = false;
1304
var i = 1, l = arguments.length;
1305
1306
for (i=1; i<l; i++) {
1307
this.setNamedItem(arguments[i]);
1308
}
1309
core.markTreeReadonly(this);
1310
};
1311
inheritFrom(core.NamedNodeMap, core.EntityNodeMap);
1312
1313
core.Element = function Element(document, tagName) {
1314
this._ownerDocument = document;
1315
core.Node.call(this, document);
1316
this._nodeName = tagName;
1317
this._tagName = tagName;
1318
};
1319
1320
inheritFrom(core.Node, core.Element, {
1321
1322
get nodeValue() { return null;},
1323
set nodeValue(value) { /* do nothing */ },
1324
get tagName() {
1325
if (this.nodeType === ELEMENT_NODE &&
1326
this._ownerDocument &&
1327
this._ownerDocument._doctype &&
1328
this._ownerDocument._doctype.name.toLowerCase().indexOf("html") !== -1)
1329
{
1330
return this.nodeName.toUpperCase();
1331
}
1332
return this.nodeName;
1333
},
1334
nodeType : ELEMENT_NODE,
1335
get attributes() {
1336
return this._attributes;
1337
},
1338
1339
/* returns string */
1340
getAttribute: function(/* string */ name) {
1341
var attribute = this._attributes.$getNode(null, name);
1342
if (attribute) {
1343
return attribute.value;
1344
}
1345
return null;
1346
},
1347
1348
setAttribute: function(/* string */ name, /* string */ value) {
1349
if (this._ownerDocument) {
1350
var attr = this._ownerDocument.createAttribute(name);
1351
attr.value = value;
1352
attr._ownerElement = this;
1353
attr._created = true;
1354
this._attributes.$setNode(attr);
1355
}
1356
1357
}, //raises: function(DOMException) {},
1358
1359
removeAttribute: function(/* string */ name) {
1360
this._attributes.$remove(null, name);
1361
}, // raises: function(DOMException) {},
1362
1363
/* returns Attr */
1364
getAttributeNode: function(/* string */ name) {
1365
return this._attributes.$getNode(null, name);
1366
},
1367
1368
/* returns Attr */
1369
setAttributeNode: function(/* Attr */ newAttr) {
1370
var prevNode = this._attributes.$getNode(null, newAttr.name);
1371
if (prevNode) {
1372
prevNode._ownerElement = null;
1373
}
1374
1375
newAttr._ownerElement = this;
1376
this._attributes.$setNode(newAttr);
1377
1378
return (prevNode && prevNode.specified) ? prevNode : null;
1379
}, // raises: function(DOMException) {},
1380
1381
/* returns Attr */
1382
removeAttributeNode: function(/* Attr */ oldAttr) {
1383
var ret = this._attributes.$removeNode(oldAttr);
1384
1385
if (ret !== null) {
1386
return ret;
1387
}
1388
1389
throw new core.DOMException(NOT_FOUND_ERR);
1390
}, //raises: function(DOMException) {},
1391
1392
/* returns NodeList */
1393
getElementsByTagName: function(/* string */ name) {
1394
name = name.toLowerCase();
1395
1396
function filterByTagName(child) {
1397
child = (child.nodeType === ENTITY_REFERENCE_NODE) ?
1398
child._entity :
1399
child;
1400
1401
if (child.nodeName && child.nodeType === ELEMENT_NODE) {
1402
return name === "*" || (child.nodeName.toLowerCase() === name);
1403
}
1404
1405
return false;
1406
}
1407
return new core.NodeList(this._ownerDocument || this, core.mapper(this, filterByTagName, true));
1408
},
1409
});
1410
1411
core.DocumentFragment = function DocumentFragment(document) {
1412
core.Node.call(this, document);
1413
this._nodeName = this._tagName = "#document-fragment";
1414
};
1415
inheritFrom(core.Node, core.DocumentFragment, {
1416
nodeType : DOCUMENT_FRAGMENT_NODE,
1417
get nodeValue() { return null;},
1418
set nodeValue(unused) { /* do nothing */ },
1419
get attributes() { return null;}
1420
});
1421
1422
core.ProcessingInstruction = function ProcessingInstruction(document, target, data) {
1423
this._ownerDocument = document;
1424
core.Node.call(this, document);
1425
this._nodeName = target;
1426
this._tagName = target;
1427
this._target = target;
1428
this._nodeValue = data;
1429
}
1430
inheritFrom(core.Node, core.ProcessingInstruction, {
1431
nodeType : PROCESSING_INSTRUCTION_NODE,
1432
get target() { return this._target;},
1433
set target(value) { throw new core.DOMException(1);},
1434
get nodeValue() { return this._nodeValue;},
1435
set nodeValue(value) { this._nodeValue = value},
1436
get data() { return this._nodeValue;},
1437
set data(unused) { throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);},
1438
get attributes() { return null;}
1439
1440
});
1441
1442
core.Document = function Document(options) {
1443
if (!options) {
1444
options = {};
1445
}
1446
else if (typeof options == 'string') {
1447
options = {
1448
name: options
1449
};
1450
}
1451
core.Node.call(this, "#document");
1452
this._nodeName = this._tagName = "#document";
1453
this._contentType = options.contentType || "text/xml";
1454
this._doctype = options._doctype;
1455
this._implementation = options.implementation || new (core.DOMImplementation)();
1456
this._documentElement = null;
1457
this._ids = Object.create(null);
1458
this._attached = true;
1459
this._ownerDocument = this;
1460
this._readonly = false;
1461
};
1462
1463
1464
var tagRegEx = /[^\w:\d_\.-]+/i;
1465
var entRegEx = /[^\w\d_\-&;]+/;
1466
var invalidAttrRegEx = /[\s"'>/=\u0000-\u001A]/;
1467
1468
inheritFrom(core.Node, core.Document, {
1469
nodeType : DOCUMENT_NODE,
1470
_elementBuilders : { },
1471
_defaultElementBuilder: function(document, tagName) {
1472
return new core.Element(document, tagName);
1473
},
1474
get contentType() { return this._contentType;},
1475
get doctype() { return this._doctype || null;},
1476
set doctype(doctype) { this._doctype = doctype;},
1477
get documentElement() {
1478
if (this._documentElement) {
1479
return this._documentElement;
1480
} else {
1481
var children = this._childNodes, len = this._childNodes.length, i=0;
1482
for (i;i<len;i++) {
1483
if (children[i].nodeType === ELEMENT_NODE) {
1484
this._documentElement = children[i];
1485
return children[i];
1486
}
1487
}
1488
return null;
1489
}
1490
},
1491
1492
get implementation() { return this._implementation;},
1493
set implementation(implementation) { this._implementation = implementation;},
1494
get nodeName() { return '#document'; },
1495
get tagName() {
1496
return null;
1497
},
1498
get nodeValue() { return null; },
1499
set nodeValue(unused) { /* noop */ },
1500
get attributes() { return null;},
1501
get ownerDocument() { return null;},
1502
get readonly() { return this._readonly;},
1503
1504
/* returns Element */
1505
_createElementNoTagNameValidation: function(/*string*/ tagName) {
1506
var lower = tagName.toLowerCase();
1507
var element = (this._elementBuilders[lower] || this._defaultElementBuilder)(this, tagName);
1508
1509
// Check for and introduce default elements
1510
if (this._doctype && this._doctype._attributes && this._doctype.name.toLowerCase() !== "html") {
1511
var attrElement = this._doctype._attributes.getNamedItem(tagName);
1512
if (attrElement && attrElement._childNodes) {
1513
1514
var attrs = attrElement.attributes;
1515
var attr, len = attrs.length, defaultAttr;
1516
for (var i = 0; i < len; i++) {
1517
defaultAttr = attrs[i];
1518
if (defaultAttr) {
1519
attr = this.createAttribute(defaultAttr.name);
1520
attr.value = defaultAttr.value;
1521
element.setAttributeNode(attr);
1522
attr._specified = false;
1523
attr._created = true;
1524
}
1525
}
1526
}
1527
}
1528
1529
element._created = true;
1530
return element;
1531
},
1532
1533
/* returns Element */
1534
createElement: function(/* string */ tagName) {
1535
tagName = String(tagName);
1536
1537
var c = [];
1538
1539
if (tagName.length === 0 || (c = tagName.match(tagRegEx))) {
1540
throw new core.DOMException(INVALID_CHARACTER_ERR, 'Invalid character in tag name: ' + c.pop());
1541
}
1542
1543
return this._createElementNoTagNameValidation(tagName);
1544
}, //raises: function(DOMException) {},
1545
1546
/* returns DocumentFragment */
1547
createDocumentFragment: function() {
1548
return new core.DocumentFragment(this);
1549
},
1550
1551
/* returns Text */
1552
createTextNode: function(/* string */ data) {
1553
return new core.Text(this,data);
1554
},
1555
1556
/* returns Comment */
1557
createComment: function(/* string */ data) {
1558
return new core.Comment(this,data);
1559
},
1560
1561
/* returns CDATASection */
1562
createCDATASection: function(/* string */ data) {
1563
if (this._doctype && this._doctype.name === "html") {
1564
throw new core.DOMException(NOT_SUPPORTED_ERR);
1565
}
1566
1567
return new core.CDATASection(this,data);
1568
}, // raises: function(DOMException) {},
1569
1570
/* returns ProcessingInstruction */
1571
createProcessingInstruction: function(/* string */ target,/* string */ data) {
1572
1573
if (this._doctype && this._doctype.name === "html") {
1574
throw new core.DOMException(NOT_SUPPORTED_ERR);
1575
}
1576
1577
if (target.match(tagRegEx) || !target || !target.length) {
1578
throw new core.DOMException(INVALID_CHARACTER_ERR);
1579
}
1580
1581
return new core.ProcessingInstruction(this, target, data);
1582
}, // raises: function(DOMException) {},
1583
1584
/* returns Attr */
1585
createAttribute: function(/* string */ name) {
1586
if (!name || !name.length || name.match(invalidAttrRegEx) ) {
1587
throw new core.DOMException(INVALID_CHARACTER_ERR, "attribute name: " + name);
1588
}
1589
return new core.Attr(this, name,false);
1590
}, // raises: function(DOMException) {},
1591
1592
/* returns EntityReference */
1593
createEntityReference: function(/* string */ name) {
1594
1595
if (this._doctype && this._doctype.name === "html") {
1596
throw new core.DOMException(NOT_SUPPORTED_ERR);
1597
}
1598
1599
name = name.replace(/[&;]/g,"");
1600
if (!name || !name.length) {
1601
throw new core.DOMException(INVALID_CHARACTER_ERR);
1602
}
1603
1604
if (name.match(tagRegEx)) {
1605
throw new core.DOMException(INVALID_CHARACTER_ERR);
1606
}
1607
1608
var entity;
1609
if (this._doctype && this._doctype.entities) {
1610
entity = this._doctype.entities.getNamedItem(name);
1611
} else {
1612
entity = null;
1613
}
1614
1615
var ref = new core.EntityReference(this, entity);
1616
1617
ref._entityName = name;
1618
1619
return ref;
1620
}, //raises: function(DOMException) {},
1621
1622
/* returns Entity */
1623
createEntityNode : function(/* string */ name)
1624
{
1625
1626
if (name.match(entRegEx) || !name || !name.length) {
1627
throw new core.DOMException(INVALID_CHARACTER_ERR);
1628
}
1629
1630
var ret = new core.Entity(this, name);
1631
ret._readonly = false;// TODO: fix me please.
1632
1633
for (var i=1;i<arguments.length;i++)
1634
{
1635
ret.appendChild(arguments[i]);
1636
}
1637
1638
core.markTreeReadonly(ret);
1639
1640
return ret;
1641
},
1642
1643
/* returns Notation */
1644
createNotationNode : function(/* string */ name,/* string */ publicId,/* string */ systemId)
1645
{
1646
1647
if (name.match(entRegEx) || !name || !name.length) {
1648
throw new core.DOMException(INVALID_CHARACTER_ERR);
1649
}
1650
1651
var ret = new core.Notation(this, name, publicId, systemId);
1652
ret._readonly = false;// TODO: fix me please.
1653
1654
for (var i=3;i<arguments.length;i++)
1655
{
1656
ret.appendChild(arguments[i]);
1657
}
1658
1659
core.markTreeReadonly(ret);
1660
1661
return ret;
1662
},
1663
1664
appendChild : function(/* Node */ arg) {
1665
if (this.documentElement && arg.nodeType == ELEMENT_NODE) {
1666
throw new core.DOMException(HIERARCHY_REQUEST_ERR);
1667
}
1668
return core.Node.prototype.appendChild.call(this, arg);
1669
},
1670
1671
removeChild : function(/* Node */ arg) {
1672
var ret = core.Node.prototype.removeChild.call(this, arg);
1673
if (arg == this._documentElement) {
1674
this._documentElement = null;// force a recalculation
1675
}
1676
return ret;
1677
},
1678
1679
/* returns NodeList */
1680
getElementsByTagName: function(/* string */ name) {
1681
function filterByTagName(child) {
1682
if (child.nodeType && child.nodeType === ENTITY_REFERENCE_NODE)
1683
{
1684
child = child._entity;
1685
}
1686
1687
if (child.nodeName && child.nodeType === ELEMENT_NODE)
1688
{
1689
if (name === "*") {
1690
return true;
1691
1692
// case insensitivity for html
1693
} else if (child._ownerDocument && child._ownerDocument._doctype &&
1694
//child._ownerDocument._doctype.name === "html" &&
1695
child.nodeName.toLowerCase() === name.toLowerCase())
1696
{
1697
return true;
1698
} else if (child.nodeName.toLowerCase() === name.toLowerCase()) {
1699
return true;
1700
}
1701
}
1702
return false;
1703
}
1704
return new core.NodeList(this.documentElement || this, core.mapper(this, filterByTagName, true));
1705
}
1706
});
1707
1708
core.CharacterData = function CharacterData(document, value) {
1709
core.Node.call(this, document);
1710
1711
this._nodeValue = value + "";
1712
};
1713
inheritFrom(core.Node, core.CharacterData, {
1714
1715
get data() { return this._nodeValue;},
1716
set data(data) {
1717
1718
// readonly
1719
if (this._readonly === true) {
1720
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
1721
}
1722
1723
this._nodeValue = data;
1724
},
1725
1726
/* returns int */
1727
get length() { return this._nodeValue.length || 0;},
1728
1729
/* returns string */
1730
substringData: function(/* int */ offset, /* int */ count) {
1731
1732
if (count < 0 || offset < 0 || offset > this._nodeValue.length) {
1733
throw new core.DOMException(INDEX_SIZE_ERR);
1734
}
1735
1736
return (this._nodeValue.length < offset + count) ?
1737
this._nodeValue.substring(offset) :
1738
this._nodeValue.substring(offset, offset+count);
1739
1740
}, // raises: function(DOMException) {},
1741
1742
/* returns string */
1743
appendData: function(/* string */ arg) {
1744
1745
// readonly
1746
if (this._readonly === true) {
1747
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
1748
}
1749
1750
this._nodeValue+=arg;
1751
return this._nodeValue;
1752
}, // raises: function(DOMException) {},
1753
1754
/* returns string */
1755
insertData: function(/* int */ offset, /* string */ arg) {
1756
1757
// readonly
1758
if (this._readonly === true) {
1759
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
1760
}
1761
1762
if (offset < 0 || offset > this._nodeValue.length) {
1763
throw new core.DOMException(INDEX_SIZE_ERR);
1764
}
1765
1766
var start = this._nodeValue.substring(0,offset);
1767
var end = this._nodeValue.substring(offset);
1768
1769
this._nodeValue = start + arg + end;
1770
1771
}, //raises: function(DOMException) {},
1772
1773
/* returns void */
1774
deleteData: function(/* int */ offset, /* int */ count) {
1775
1776
// readonly
1777
if (this._readonly === true) {
1778
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
1779
}
1780
1781
if (offset < 0 ||
1782
offset > this._nodeValue.length ||
1783
count < 0)
1784
{
1785
throw new core.DOMException(INDEX_SIZE_ERR);
1786
}
1787
1788
var start = this._nodeValue.substring(0,offset);
1789
1790
this._nodeValue = (offset+count<this._nodeValue.length) ?
1791
start + this._nodeValue.substring(offset+count) :
1792
start;
1793
}, // raises: function(DOMException) {},
1794
1795
/* returns void */
1796
replaceData: function(/* int */ offset, /* int */ count, /* string */ arg) {
1797
1798
// readonly
1799
if (this._readonly === true) {
1800
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
1801
}
1802
1803
count = (offset+count > this._nodeValue.length) ?
1804
this.nodeValue.length-offset :
1805
count;
1806
1807
if (offset < 0 ||
1808
offset > this._nodeValue.length ||
1809
count < 0 /*||
1810
offset+count > this._nodeValue.length*/)
1811
{
1812
throw new core.DOMException(INDEX_SIZE_ERR);
1813
}
1814
1815
var start = this._nodeValue.substring(0,offset);
1816
var end = this._nodeValue.substring(offset+count);
1817
1818
this._nodeValue = start + arg + end;
1819
} // raises: function(DOMException) {},
1820
});
1821
1822
1823
core.Attr = function Attr(document, name, value) {
1824
core.Node.call(this, document);
1825
this._nodeValue = value;
1826
this._name = name;
1827
this._specified = (value) ? true : false;
1828
this._tagName = name;
1829
this._nodeName = name;
1830
1831
// Proactively set some level 2 information so that AttributeList
1832
// can operate.
1833
this._namespaceURI = null;
1834
this._nodeName = name;
1835
this._localName = name;
1836
this._prefix = null;
1837
};
1838
inheritFrom(core.Node, core.Attr, {
1839
nodeType : ATTRIBUTE_NODE,
1840
get nodeValue() {
1841
var val = '';
1842
for (var i=0,len=this._childNodes.length;i<len;i++) {
1843
var child = this._childNodes[i];
1844
if (child.nodeType === ENTITY_REFERENCE_NODE) {
1845
val += Array.prototype.reduce.call(child.childNodes, function(prev, c) {
1846
return prev += (c.nodeValue || c);
1847
}, '');
1848
} else {
1849
val += child.nodeValue;
1850
}
1851
}
1852
return val;
1853
},
1854
set nodeValue(value) {
1855
// readonly
1856
if (this._readonly) {
1857
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
1858
}
1859
1860
this._childNodes._resetTo([this._ownerDocument.createTextNode(value)]);
1861
this._modified();
1862
this._specified = true;
1863
var prev = this._nodeValue;
1864
this._nodeValue = value;
1865
if (this._ownerElement) {
1866
this._ownerElement._attrModified(this._name, value, prev);
1867
}
1868
},
1869
get name() { return this._name;},
1870
get specified() { return this._specified },
1871
get value() {
1872
return this.nodeValue;
1873
},
1874
set value(value) {
1875
this.nodeValue = value;
1876
},
1877
get parentNode() { return null;},
1878
get attributes() { return null;},
1879
1880
insertBefore : function(/* Node */ newChild, /* Node*/ refChild){
1881
if (newChild.nodeType === CDATA_SECTION_NODE ||
1882
newChild.nodeType === ELEMENT_NODE)
1883
{
1884
throw new core.DOMException(HIERARCHY_REQUEST_ERR);
1885
}
1886
1887
return core.Node.prototype.insertBefore.call(this, newChild, refChild);
1888
},
1889
1890
appendChild : function(/* Node */ arg) {
1891
1892
if (arg.nodeType === CDATA_SECTION_NODE ||
1893
arg.nodeType === ELEMENT_NODE)
1894
{
1895
throw new core.DOMException(HIERARCHY_REQUEST_ERR);
1896
}
1897
1898
return core.Node.prototype.appendChild.call(this, arg);
1899
}
1900
1901
});
1902
1903
core.Text = function Text(document, text, readonly) {
1904
core.CharacterData.call(this, document, text);
1905
this._nodeName = "#text";
1906
this._readonly = readonly ? true : false
1907
};
1908
inheritFrom(core.CharacterData, core.Text, {
1909
nodeType : TEXT_NODE,
1910
get attributes() { return null;},
1911
1912
/* returns Text */
1913
splitText: function(offset) {
1914
1915
// readonly
1916
if (this._readonly) {
1917
throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
1918
}
1919
1920
if (offset < 0 || offset > this._nodeValue.length) {
1921
throw new core.DOMException(INDEX_SIZE_ERR);
1922
}
1923
1924
var newText = this._nodeValue.substring(offset);
1925
this._nodeValue = this._nodeValue.substring(0, offset);
1926
var newNode = this._ownerDocument.createTextNode(newText);
1927
1928
if(this._parentNode.lastChild === this) {
1929
this._parentNode.appendChild(newNode);
1930
} else {
1931
this._parentNode.insertBefore(newNode, this.nextSibling);
1932
}
1933
1934
return newNode;
1935
}, //raises: function(DOMException) {},
1936
toString: function() {
1937
return this.nodeName;
1938
}
1939
});
1940
1941
1942
core.Comment = function Comment(document, text) {
1943
core.Text.call(this, document, text);
1944
this._nodeName = "#comment";
1945
this._tagName = "#comment";
1946
};
1947
inheritFrom(core.Text, core.Comment, {
1948
nodeType : COMMENT_NODE
1949
});
1950
1951
1952
core.CDATASection = function CDATASection(document, value) {
1953
core.Text.call(this, document, value);
1954
this._nodeName = "#cdata-section";
1955
};
1956
inheritFrom(core.Text, core.CDATASection, {
1957
nodeType : CDATA_SECTION_NODE
1958
});
1959
1960
core.DocumentType = function DocumentType(document, name, entities, notations, attributes) {
1961
core.Node.call(this, document);
1962
this._name = name;
1963
this._tagName = name;
1964
this._nodeName = name;
1965
this._entities = entities || new core.EntityNodeMap(document);
1966
this._notations = notations || new core.NotationNodeMap(document);
1967
1968
core.markTreeReadonly(this._notations);
1969
1970
this._attributes = attributes || new AttributeList(document);
1971
};
1972
inheritFrom(core.Node, core.DocumentType, {
1973
nodeType : DOCUMENT_TYPE_NODE,
1974
get nodeValue() { return null;},
1975
set nodeValue(unused) { /* do nothing */ },
1976
get name() { return this._name;},
1977
get entities() { return this._entities;},
1978
get notations() { return this._notations;},
1979
get attributes() { return null;}
1980
});
1981
1982
1983
core.Notation = function Notation(document, name, publicId, systemId){
1984
core.Node.call(this, document);
1985
this._name = name;
1986
this._nodeName = name;
1987
this._publicId = publicId || null;
1988
this._systemId = systemId || null;
1989
this._nodeValue = null;
1990
};
1991
inheritFrom(core.Node, core.Notation, {
1992
nodeType : NOTATION_NODE,
1993
get publicId() { return this._publicId;},
1994
get systemId() { return this._systemId;},
1995
get name() { return this._name || this._nodeName;},
1996
get attributes() { /* as per spec */ return null;},
1997
set nodeValue(unused) { /* intentionally left blank */ },
1998
get nodeValue() { return this._nodeValue;},
1999
});
2000
2001
2002
core.Entity = function Entity(document, name) {
2003
core.Node.call(this, document);
2004
this._name = name;
2005
this._nodeName = name;
2006
this._tagName = name;
2007
this._publicId = null;
2008
this._systemId = null;
2009
this._notationName = null;
2010
this._readonly = true;
2011
};
2012
inheritFrom(core.Node, core.Entity, {
2013
nodeType : ENTITY_NODE,
2014
get nodeValue() { return null;},
2015
set nodeValue(unused) {
2016
// readonly
2017
if (this._readonly === true) {
2018
// TODO: is this needed?
2019
// throw new DOMException(NO_MODIFICATION_ALLOWED_ERR);
2020
}
2021
/* do nothing */
2022
},
2023
get name() { return this._name },
2024
get publicId() { return this._publicId;},
2025
get systemId() { return this._systemId;},
2026
2027
set publicId(publicId) { this._publicId = publicId;},
2028
set systemId(systemId) { this._systemId = systemId;},
2029
set notationName(notationName) { this._notationName = notationName;},
2030
2031
get notationName() { return this._notationName;},
2032
get attributes() { return null;},
2033
2034
});
2035
2036
2037
core.EntityReference = function EntityReference(document, entity) {
2038
core.Node.call(this, document);
2039
this._entity = entity;
2040
this._nodeName = (entity) ? entity.name : null;
2041
this._readonly = true;
2042
};
2043
inheritFrom(core.Node, core.EntityReference, {
2044
nodeType : ENTITY_REFERENCE_NODE,
2045
get nodeValue() { return (this._entity) ? this._entity.nodeValue : null;},
2046
set nodeValue(unused) {
2047
// readonly
2048
if (this._readonly === true) {
2049
// TODO: is this needed?
2050
//throw new DOMException(NO_MODIFICATION_ALLOWED_ERR);
2051
}
2052
2053
/* do nothing */
2054
},
2055
get attributes() { return null;},
2056
2057
// Proxy to the entity
2058
get nodeName() { return this._entityName;},
2059
get firstChild() { return this._entity.firstChild || null;},
2060
get childNodes() { return this._entity.childNodes;},
2061
get lastChild() { return this._entity.lastChild || null;},
2062
2063
});
2064
2065
exports.dom = { "level1" : { "core" : core }};
2066
2067