forked from jeresig/env-js
/
implementation.js
591 lines (503 loc) · 22.8 KB
/
implementation.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
//DOMImplementation
$debug("Defining DOMImplementation");
/**
* @class DOMImplementation - provides a number of methods for performing operations
* that are independent of any particular instance of the document object model.
*
* @author Jon van Noort (jon@webarcana.com.au)
*/
var DOMImplementation = function() {
this.preserveWhiteSpace = false; // by default, ignore whitespace
this.namespaceAware = true; // by default, handle namespaces
this.errorChecking = true; // by default, test for exceptions
};
__extend__(DOMImplementation.prototype,{
// @param feature : string - The package name of the feature to test.
// the legal only values are "XML" and "CORE" (case-insensitive).
// @param version : string - This is the version number of the package
// name to test. In Level 1, this is the string "1.0".*
// @return : boolean
hasFeature : function(feature, version) {
var ret = false;
if (feature.toLowerCase() == "xml") {
ret = (!version || (version == "1.0") || (version == "2.0"));
}
else if (feature.toLowerCase() == "core") {
ret = (!version || (version == "2.0"));
}
else if (feature == "http://www.w3.org/TR/SVG11/feature#BasicStructure") {
ret = (version == "1.1");
}
return ret;
},
createDocumentType : function(qname, publicid, systemid){
return new DOMDocumentType();
},
createDocument : function(nsuri, qname, doctype){
//TODO - this currently returns an empty doc
//but needs to handle the args
return new Document($implementation, null);
},
createHTMLDocument : function(title){
var doc = new HTMLDocument($implementation, null, "");
var html = doc.createElement("html"); doc.appendChild(html);
var head = doc.createElement("head"); html.appendChild(head);
var body = doc.createElement("body"); html.appendChild(body);
var t = doc.createElement("title"); head.appendChild(t);
if( title) {
t.appendChild(doc.createTextNode(title));
}
return doc;
},
translateErrCode : function(code) {
//convert DOMException Code to human readable error message;
var msg = "";
switch (code) {
case DOMException.INDEX_SIZE_ERR : // 1
msg = "INDEX_SIZE_ERR: Index out of bounds";
break;
case DOMException.DOMSTRING_SIZE_ERR : // 2
msg = "DOMSTRING_SIZE_ERR: The resulting string is too long to fit in a DOMString";
break;
case DOMException.HIERARCHY_REQUEST_ERR : // 3
msg = "HIERARCHY_REQUEST_ERR: The Node can not be inserted at this location";
break;
case DOMException.WRONG_DOCUMENT_ERR : // 4
msg = "WRONG_DOCUMENT_ERR: The source and the destination Documents are not the same";
break;
case DOMException.INVALID_CHARACTER_ERR : // 5
msg = "INVALID_CHARACTER_ERR: The string contains an invalid character";
break;
case DOMException.NO_DATA_ALLOWED_ERR : // 6
msg = "NO_DATA_ALLOWED_ERR: This Node / NodeList does not support data";
break;
case DOMException.NO_MODIFICATION_ALLOWED_ERR : // 7
msg = "NO_MODIFICATION_ALLOWED_ERR: This object cannot be modified";
break;
case DOMException.NOT_FOUND_ERR : // 8
msg = "NOT_FOUND_ERR: The item cannot be found";
break;
case DOMException.NOT_SUPPORTED_ERR : // 9
msg = "NOT_SUPPORTED_ERR: This implementation does not support function";
break;
case DOMException.INUSE_ATTRIBUTE_ERR : // 10
msg = "INUSE_ATTRIBUTE_ERR: The Attribute has already been assigned to another Element";
break;
// Introduced in DOM Level 2:
case DOMException.INVALID_STATE_ERR : // 11
msg = "INVALID_STATE_ERR: The object is no longer usable";
break;
case DOMException.SYNTAX_ERR : // 12
msg = "SYNTAX_ERR: Syntax error";
break;
case DOMException.INVALID_MODIFICATION_ERR : // 13
msg = "INVALID_MODIFICATION_ERR: Cannot change the type of the object";
break;
case DOMException.NAMESPACE_ERR : // 14
msg = "NAMESPACE_ERR: The namespace declaration is incorrect";
break;
case DOMException.INVALID_ACCESS_ERR : // 15
msg = "INVALID_ACCESS_ERR: The object does not support this function";
break;
default :
msg = "UNKNOWN: Unknown Exception Code ("+ code +")";
}
return msg;
}
});
/**
* Defined 'globally' to this scope. Remember everything is wrapped in a closure so this doesnt show up
* in the outer most global scope.
*/
/**
* process SAX events
*
* @author Jon van Noort (jon@webarcana.com.au), David Joham (djoham@yahoo.com) and Scott Severtson
*
* @param impl : DOMImplementation
* @param doc : DOMDocument - the Document to contain the parsed XML string
* @param p : XMLP - the SAX Parser
*
* @return : DOMDocument
*/
/*function __parseLoop__(impl, doc, p, isWindowDocument) {
var iEvt, iNode, iAttr, strName;
var iNodeParent = doc;
var el_close_count = 0;
var entitiesList = new Array();
var textNodesList = new Array();
// if namespaceAware, add default namespace
if (impl.namespaceAware) {
var iNS = doc.createNamespace(""); // add the default-default namespace
iNS.value = "http://www.w3.org/2000/xmlns/";
doc._namespaces.setNamedItem(iNS);
}
// loop until SAX parser stops emitting events
var q = 0;
while(true) {
// get next event
iEvt = p.next();
if (iEvt == XMLP._ELM_B) { // Begin-Element Event
var pName = p.getName(); // get the Element name
pName = trim(pName, true, true); // strip spaces from Element name
if(pName.toLowerCase() == 'script')
p.replaceEntities = false;
if (!impl.namespaceAware) {
iNode = doc.createElement(p.getName()); // create the Element
// add attributes to Element
for(var i = 0; i < p.getAttributeCount(); i++) {
strName = p.getAttributeName(i); // get Attribute name
iAttr = iNode.getAttributeNode(strName); // if Attribute exists, use it
if(!iAttr) {
iAttr = doc.createAttribute(strName); // otherwise create it
}
iAttr.value = p.getAttributeValue(i); // set Attribute value
iNode.setAttributeNode(iAttr); // attach Attribute to Element
}
}
else { // Namespace Aware
// create element (with empty namespaceURI,
// resolve after namespace 'attributes' have been parsed)
iNode = doc.createElementNS("", p.getName());
// duplicate ParentNode's Namespace definitions
iNode._namespaces = __cloneNamedNodes__(iNodeParent._namespaces, iNode);
// add attributes to Element
for(var i = 0; i < p.getAttributeCount(); i++) {
strName = p.getAttributeName(i); // get Attribute name
// if attribute is a namespace declaration
if (__isNamespaceDeclaration__(strName)) {
// parse Namespace Declaration
var namespaceDec = __parseNSName__(strName);
if (strName != "xmlns") {
iNS = doc.createNamespace(strName); // define namespace
}
else {
iNS = doc.createNamespace(""); // redefine default namespace
}
iNS.value = p.getAttributeValue(i); // set value = namespaceURI
iNode._namespaces.setNamedItem(iNS); // attach namespace to namespace collection
}
else { // otherwise, it is a normal attribute
iAttr = iNode.getAttributeNode(strName); // if Attribute exists, use it
if(!iAttr) {
iAttr = doc.createAttributeNS("", strName); // otherwise create it
}
iAttr.value = p.getAttributeValue(i); // set Attribute value
iNode.setAttributeNodeNS(iAttr); // attach Attribute to Element
if (__isIdDeclaration__(strName)) {
iNode.id = p.getAttributeValue(i); // cache ID for getElementById()
}
}
}
// resolve namespaceURIs for this Element
if (iNode._namespaces.getNamedItem(iNode.prefix)) {
iNode.namespaceURI = iNode._namespaces.getNamedItem(iNode.prefix).value;
}
// for this Element's attributes
for (var i = 0; i < iNode.attributes.length; i++) {
if (iNode.attributes.item(i).prefix != "") { // attributes do not have a default namespace
if (iNode._namespaces.getNamedItem(iNode.attributes.item(i).prefix)) {
iNode.attributes.item(i).namespaceURI = iNode._namespaces.getNamedItem(iNode.attributes.item(i).prefix).value;
}
}
}
}
// if this is the Root Element
if (iNodeParent.nodeType == DOMNode.DOCUMENT_NODE) {
iNodeParent._documentElement = iNode; // register this Element as the Document.documentElement
}
iNodeParent.appendChild(iNode); // attach Element to parentNode
iNodeParent = iNode; // descend one level of the DOM Tree
}
else if(iEvt == XMLP._ELM_E) { // End-Element Event
__endHTMLElement__(iNodeParent, doc, p);
iNodeParent = iNodeParent.parentNode; // ascend one level of the DOM Tree
}
else if(iEvt == XMLP._ELM_EMP) { // Empty Element Event
pName = p.getName(); // get the Element name
pName = trim(pName, true, true); // strip spaces from Element name
if (!impl.namespaceAware) {
iNode = doc.createElement(pName); // create the Element
// add attributes to Element
for(var i = 0; i < p.getAttributeCount(); i++) {
strName = p.getAttributeName(i); // get Attribute name
iAttr = iNode.getAttributeNode(strName); // if Attribute exists, use it
if(!iAttr) {
iAttr = doc.createAttribute(strName); // otherwise create it
}
iAttr.value = p.getAttributeValue(i); // set Attribute value
iNode.setAttributeNode(iAttr); // attach Attribute to Element
}
}
else { // Namespace Aware
// create element (with empty namespaceURI,
// resolve after namespace 'attributes' have been parsed)
iNode = doc.createElementNS("", p.getName());
// duplicate ParentNode's Namespace definitions
iNode._namespaces = __cloneNamedNodes__(iNodeParent._namespaces, iNode);
// add attributes to Element
for(var i = 0; i < p.getAttributeCount(); i++) {
strName = p.getAttributeName(i); // get Attribute name
// if attribute is a namespace declaration
if (__isNamespaceDeclaration__(strName)) {
// parse Namespace Declaration
var namespaceDec = __parseNSName__(strName);
if (strName != "xmlns") {
iNS = doc.createNamespace(strName); // define namespace
}
else {
iNS = doc.createNamespace(""); // redefine default namespace
}
iNS.value = p.getAttributeValue(i); // set value = namespaceURI
iNode._namespaces.setNamedItem(iNS); // attach namespace to namespace collection
}
else { // otherwise, it is a normal attribute
iAttr = iNode.getAttributeNode(strName); // if Attribute exists, use it
if(!iAttr) {
iAttr = doc.createAttributeNS("", strName); // otherwise create it
}
iAttr.value = p.getAttributeValue(i); // set Attribute value
iNode.setAttributeNodeNS(iAttr); // attach Attribute to Element
if (__isIdDeclaration__(strName)) {
iNode.id = p.getAttributeValue(i); // cache ID for getElementById()
}
}
}
// resolve namespaceURIs for this Element
if (iNode._namespaces.getNamedItem(iNode.prefix)) {
iNode.namespaceURI = iNode._namespaces.getNamedItem(iNode.prefix).value;
}
// for this Element's attributes
for (var i = 0; i < iNode.attributes.length; i++) {
if (iNode.attributes.item(i).prefix != "") { // attributes do not have a default namespace
if (iNode._namespaces.getNamedItem(iNode.attributes.item(i).prefix)) {
iNode.attributes.item(i).namespaceURI = iNode._namespaces.getNamedItem(iNode.attributes.item(i).prefix).value;
}
}
}
}
// if this is the Root Element
if (iNodeParent.nodeType == DOMNode.DOCUMENT_NODE) {
iNodeParent._documentElement = iNode; // register this Element as the Document.documentElement
}
iNodeParent.appendChild(iNode); // attach Element to parentNode
__endHTMLElement__(iNode, doc, p);
}
else if(iEvt == XMLP._TEXT || iEvt == XMLP._ENTITY) { // TextNode and entity Events
// get Text content
var pContent = p.getContent().substring(p.getContentBegin(), p.getContentEnd());
if (!impl.preserveWhiteSpace ) {
if (trim(pContent, true, true) == "") {
pContent = ""; //this will cause us not to create the text node below
}
}
if (pContent.length > 0) { // ignore empty TextNodes
var textNode = doc.createTextNode(pContent);
iNodeParent.appendChild(textNode); // attach TextNode to parentNode
//the sax parser breaks up text nodes when it finds an entity. For
//example hello<there will fire a text, an entity and another text
//this sucks for the dom parser because it looks to us in this logic
//as three text nodes. I fix this by keeping track of the entity nodes
//and when we're done parsing, calling normalize on their parent to
//turn the multiple text nodes into one, which is what DOM users expect
//the code to do this is at the bottom of this function
if (iEvt == XMLP._ENTITY) {
entitiesList[entitiesList.length] = textNode;
}
else {
//I can't properly decide how to handle preserve whitespace
//until the siblings of the text node are built due to
//the entitiy handling described above. I don't know that this
//will be all of the text node or not, so trimming is not appropriate
//at this time. Keep a list of all the text nodes for now
//and we'll process the preserve whitespace stuff at a later time.
textNodesList[textNodesList.length] = textNode;
}
}
}
else if(iEvt == XMLP._PI) { // ProcessingInstruction Event
// attach ProcessingInstruction to parentNode
iNodeParent.appendChild(doc.createProcessingInstruction(p.getName(), p.getContent().substring(p.getContentBegin(), p.getContentEnd())));
}
else if(iEvt == XMLP._CDATA) { // CDATA Event
// get CDATA data
pContent = p.getContent().substring(p.getContentBegin(), p.getContentEnd());
if (!impl.preserveWhiteSpace) {
pContent = trim(pContent, true, true); // trim whitespace
pContent.replace(/ +/g, ' '); // collapse multiple spaces to 1 space
}
if (pContent.length > 0) { // ignore empty CDATANodes
iNodeParent.appendChild(doc.createCDATASection(pContent)); // attach CDATA to parentNode
}
}
else if(iEvt == XMLP._COMMENT) { // Comment Event
// get COMMENT data
var pContent = p.getContent().substring(p.getContentBegin(), p.getContentEnd());
if (!impl.preserveWhiteSpace) {
pContent = trim(pContent, true, true); // trim whitespace
pContent.replace(/ +/g, ' '); // collapse multiple spaces to 1 space
}
if (pContent.length > 0) { // ignore empty CommentNodes
iNodeParent.appendChild(doc.createComment(pContent)); // attach Comment to parentNode
}
}
else if(iEvt == XMLP._DTD) { // ignore DTD events
}
else if(iEvt == XMLP._ERROR) {
$error("Fatal Error: " + p.getContent() +
"\nLine: " + p.getLineNumber() +
"\nColumn: " + p.getColumnNumber() + "\n");
throw(new DOMException(DOMException.SYNTAX_ERR));
}
else if(iEvt == XMLP._NONE) { // no more events
//steven woods notes that unclosed tags are rejected elsewhere and this check
//breaks a table patching routine
//if (iNodeParent == doc) { // confirm that we have recursed back up to root
// break;
//}
//else {
// throw(new DOMException(DOMException.SYNTAX_ERR)); // one or more Tags were not closed properly
//}
break;
}
}
//normalize any entities in the DOM to a single textNode
for (var i = 0; i < entitiesList.length; i++) {
var entity = entitiesList[i];
//its possible (if for example two entities were in the
//same domnode, that the normalize on the first entitiy
//will remove the parent for the second. Only do normalize
//if I can find a parent node
var parentNode = entity.parentNode;
if (parentNode) {
parentNode.normalize();
//now do whitespace (if necessary)
//it was not done for text nodes that have entities
if(!impl.preserveWhiteSpace) {
var children = parentNode.childNodes;
for ( var j = 0; j < children.length; j++) {
var child = children.item(j);
if (child.nodeType == DOMNode.TEXT_NODE) {
var childData = child.data;
childData.replace(/\s/g, ' ');
child.data = childData;
}
}
}
}
}
//do the preserve whitespace processing on the rest of the text nodes
//It's possible (due to the processing above) that the node will have been
//removed from the tree. Only do whitespace checking if parentNode is not null.
//This may duplicate the whitespace processing for some nodes that had entities in them
//but there's no way around that
if (!impl.preserveWhiteSpace) {
for (var i = 0; i < textNodesList.length; i++) {
var node = textNodesList[i];
if (node.parentNode != null) {
var nodeData = node.data;
nodeData.replace(/\s/g, ' ');
node.data = nodeData;
}
}
}
};*/
/**
* @method DOMImplementation._isNamespaceDeclaration - Return true, if attributeName is a namespace declaration
* @author Jon van Noort (jon@webarcana.com.au)
* @param attributeName : string - the attribute name
* @return : boolean
*/
function __isNamespaceDeclaration__(attributeName) {
// test if attributeName is 'xmlns'
return (attributeName.indexOf('xmlns') > -1);
};
/**
* @method DOMImplementation._isIdDeclaration - Return true, if attributeName is an id declaration
* @author Jon van Noort (jon@webarcana.com.au)
* @param attributeName : string - the attribute name
* @return : boolean
*/
function __isIdDeclaration__(attributeName) {
// test if attributeName is 'id' (case insensitive)
return attributeName?(attributeName.toLowerCase() == 'id'):false;
};
/**
* @method DOMImplementation._isValidName - Return true,
* if name contains no invalid characters
* @author Jon van Noort (jon@webarcana.com.au)
* @param name : string - the candidate name
* @return : boolean
*/
function __isValidName__(name) {
// test if name contains only valid characters
return name.match(re_validName);
};
var re_validName = /^[a-zA-Z_:][a-zA-Z0-9\.\-_:]*$/;
/**
* @method DOMImplementation._isValidString - Return true, if string does not contain any illegal chars
* All of the characters 0 through 31 and character 127 are nonprinting control characters.
* With the exception of characters 09, 10, and 13, (Ox09, Ox0A, and Ox0D)
* Note: different from _isValidName in that ValidStrings may contain spaces
* @author Jon van Noort (jon@webarcana.com.au)
* @param name : string - the candidate string
* @return : boolean
*/
function __isValidString__(name) {
// test that string does not contains invalid characters
return (name.search(re_invalidStringChars) < 0);
};
var re_invalidStringChars = /\x01|\x02|\x03|\x04|\x05|\x06|\x07|\x08|\x0B|\x0C|\x0E|\x0F|\x10|\x11|\x12|\x13|\x14|\x15|\x16|\x17|\x18|\x19|\x1A|\x1B|\x1C|\x1D|\x1E|\x1F|\x7F/;
/**
* @method DOMImplementation._parseNSName - parse the namespace name.
* if there is no colon, the
* @author Jon van Noort (jon@webarcana.com.au)
* @param qualifiedName : string - The qualified name
* @return : NSName - [
.prefix : string - The prefix part of the qname
.namespaceName : string - The namespaceURI part of the qname
]
*/
function __parseNSName__(qualifiedName) {
var resultNSName = new Object();
resultNSName.prefix = qualifiedName; // unless the qname has a namespaceName, the prefix is the entire String
resultNSName.namespaceName = "";
// split on ':'
var delimPos = qualifiedName.indexOf(':');
if (delimPos > -1) {
// get prefix
resultNSName.prefix = qualifiedName.substring(0, delimPos);
// get namespaceName
resultNSName.namespaceName = qualifiedName.substring(delimPos +1, qualifiedName.length);
}
return resultNSName;
};
/**
* @method DOMImplementation._parseQName - parse the qualified name
* @author Jon van Noort (jon@webarcana.com.au)
* @param qualifiedName : string - The qualified name
* @return : QName
*/
function __parseQName__(qualifiedName) {
var resultQName = new Object();
resultQName.localName = qualifiedName; // unless the qname has a prefix, the local name is the entire String
resultQName.prefix = "";
// split on ':'
var delimPos = qualifiedName.indexOf(':');
if (delimPos > -1) {
// get prefix
resultQName.prefix = qualifiedName.substring(0, delimPos);
// get localName
resultQName.localName = qualifiedName.substring(delimPos +1, qualifiedName.length);
}
return resultQName;
};
$debug("Initializing document.implementation");
var $implementation = new DOMImplementation();
$implementation.namespaceAware = false;
$implementation.errorChecking = false;
// Local Variables:
// espresso-indent-level:4
// c-basic-offset:4
// tab-width:4
// End: