PageRenderTime 87ms CodeModel.GetById 23ms app.highlight 58ms RepoModel.GetById 1ms app.codeStats 0ms

/testing/selenium-core/xpath/dom.js

http://datanucleus-appengine.googlecode.com/
JavaScript | 566 lines | 544 code | 6 blank | 16 comment | 4 complexity | 9f9871c728d585fb686ec0339758f5d7 MD5 | raw file
  1// Copyright 2005 Google Inc.
  2// All Rights Reserved
  3//
  4// Author: Steffen Meschkat <mesch@google.com>
  5//
  6// An XML parse and a minimal DOM implementation that just supportes
  7// the subset of the W3C DOM that is used in the XSLT implementation.
  8
  9// NOTE: The split() method in IE omits empty result strings. This is
 10// utterly annoying. So we don't use it here.
 11
 12// Resolve entities in XML text fragments. According to the DOM
 13// specification, the DOM is supposed to resolve entity references at
 14// the API level. I.e. no entity references are passed through the
 15// API. See "Entities and the DOM core", p.12, DOM 2 Core
 16// Spec. However, different browsers actually pass very different
 17// values at the API. See <http://mesch.nyc/test-xml-quote>.
 18function xmlResolveEntities(s) {
 19
 20  var parts = stringSplit(s, '&');
 21
 22  var ret = parts[0];
 23  for (var i = 1; i < parts.length; ++i) {
 24    var rp = parts[i].indexOf(';');
 25    if (rp == -1) {
 26      // no entity reference: just a & but no ;
 27      ret += parts[i];
 28      continue;
 29    }
 30
 31    var entityName = parts[i].substring(0, rp);
 32    var remainderText = parts[i].substring(rp + 1);
 33
 34    var ch;
 35    switch (entityName) {
 36      case 'lt':
 37        ch = '<';
 38        break;
 39      case 'gt':
 40        ch = '>';
 41        break;
 42      case 'amp':
 43        ch = '&';
 44        break;
 45      case 'quot':
 46        ch = '"';
 47        break;
 48      case 'apos':
 49        ch = '\'';
 50        break;
 51      case 'nbsp':
 52        ch = String.fromCharCode(160);
 53        break;
 54      default:
 55        // Cool trick: let the DOM do the entity decoding. We assign
 56        // the entity text through non-W3C DOM properties and read it
 57        // through the W3C DOM. W3C DOM access is specified to resolve
 58        // entities.
 59        var span = domCreateElement(window.document, 'span');
 60        span.innerHTML = '&' + entityName + '; ';
 61        ch = span.childNodes[0].nodeValue.charAt(0);
 62    }
 63    ret += ch + remainderText;
 64  }
 65
 66  return ret;
 67}
 68
 69var XML10_TAGNAME_REGEXP = new RegExp('^(' + XML10_NAME + ')');
 70var XML10_ATTRIBUTE_REGEXP = new RegExp(XML10_ATTRIBUTE, 'g');
 71
 72var XML11_TAGNAME_REGEXP = new RegExp('^(' + XML11_NAME + ')');
 73var XML11_ATTRIBUTE_REGEXP = new RegExp(XML11_ATTRIBUTE, 'g');
 74
 75// Parses the given XML string with our custom, JavaScript XML parser. Written
 76// by Steffen Meschkat (mesch@google.com).
 77function xmlParse(xml) {
 78  var regex_empty = /\/$/;
 79
 80  var regex_tagname;
 81  var regex_attribute;
 82  if (xml.match(/^<\?xml/)) {
 83    // When an XML document begins with an XML declaration
 84    // VersionInfo must appear.
 85    if (xml.search(new RegExp(XML10_VERSION_INFO)) == 5) {
 86      regex_tagname = XML10_TAGNAME_REGEXP;
 87      regex_attribute = XML10_ATTRIBUTE_REGEXP;
 88    } else if (xml.search(new RegExp(XML11_VERSION_INFO)) == 5) {
 89      regex_tagname = XML11_TAGNAME_REGEXP;
 90      regex_attribute = XML11_ATTRIBUTE_REGEXP;
 91    } else {
 92      // VersionInfo is missing, or unknown version number.
 93      // TODO : Fallback to XML 1.0 or XML 1.1, or just return null?
 94      alert('VersionInfo is missing, or unknown version number.');
 95    }
 96  } else {
 97    // When an XML declaration is missing it's an XML 1.0 document.
 98    regex_tagname = XML10_TAGNAME_REGEXP;
 99    regex_attribute = XML10_ATTRIBUTE_REGEXP;
100  }
101
102  var xmldoc = new XDocument();
103  var root = xmldoc;
104
105  // For the record: in Safari, we would create native DOM nodes, but
106  // in Opera that is not possible, because the DOM only allows HTML
107  // element nodes to be created, so we have to do our own DOM nodes.
108
109  // xmldoc = document.implementation.createDocument('','',null);
110  // root = xmldoc; // .createDocumentFragment();
111  // NOTE(mesch): using the DocumentFragment instead of the Document
112  // crashes my Safari 1.2.4 (v125.12).
113  var stack = [];
114
115  var parent = root;
116  stack.push(parent);
117
118  // The token that delimits a section that contains markup as
119  // content: CDATA or comments.
120  var slurp = '';
121
122  var x = stringSplit(xml, '<');
123  for (var i = 1; i < x.length; ++i) {
124    var xx = stringSplit(x[i], '>');
125    var tag = xx[0];
126    var text = xmlResolveEntities(xx[1] || '');
127
128    if (slurp) {
129      // In a "slurp" section (CDATA or comment): only check for the
130      // end of the section, otherwise append the whole text.
131      var end = x[i].indexOf(slurp);
132      if (end != -1) {
133        var data = x[i].substring(0, end);
134        parent.nodeValue += '<' + data;
135        stack.pop();
136        parent = stack[stack.length-1];
137        text = x[i].substring(end + slurp.length);
138        slurp = '';
139      } else {
140        parent.nodeValue += '<' + x[i];
141        text = null;
142      }
143
144    } else if (tag.indexOf('![CDATA[') == 0) {
145      var start = '![CDATA['.length;
146      var end = x[i].indexOf(']]>');
147      if (end != -1) {
148        var data = x[i].substring(start, end);
149        var node = domCreateCDATASection(xmldoc, data);
150        domAppendChild(parent, node);
151      } else {
152        var data = x[i].substring(start);
153        text = null;
154        var node = domCreateCDATASection(xmldoc, data);
155        domAppendChild(parent, node);
156        parent = node;
157        stack.push(node);
158        slurp = ']]>';
159      }
160
161    } else if (tag.indexOf('!--') == 0) {
162      var start = '!--'.length;
163      var end = x[i].indexOf('-->');
164      if (end != -1) {
165        var data = x[i].substring(start, end);
166        var node = domCreateComment(xmldoc, data);
167        domAppendChild(parent, node);
168      } else {
169        var data = x[i].substring(start);
170        text = null;
171        var node = domCreateComment(xmldoc, data);
172        domAppendChild(parent, node);
173        parent = node;
174        stack.push(node);
175        slurp = '-->';
176      }
177
178    } else if (tag.charAt(0) == '/') {
179      stack.pop();
180      parent = stack[stack.length-1];
181
182    } else if (tag.charAt(0) == '?') {
183      // Ignore XML declaration and processing instructions
184    } else if (tag.charAt(0) == '!') {
185      // Ignore notation and comments
186    } else {
187      var empty = tag.match(regex_empty);
188      var tagname = regex_tagname.exec(tag)[1];
189      var node = domCreateElement(xmldoc, tagname);
190
191      var att;
192      while (att = regex_attribute.exec(tag)) {
193        var val = xmlResolveEntities(att[5] || att[7] || '');
194        domSetAttribute(node, att[1], val);
195      }
196
197      domAppendChild(parent, node);
198      if (!empty) {
199        parent = node;
200        stack.push(node);
201      }
202    }
203
204    if (text && parent != root) {
205      domAppendChild(parent, domCreateTextNode(xmldoc, text));
206    }
207  }
208
209  return root;
210}
211
212// Based on <http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/
213// core.html#ID-1950641247>
214var DOM_ELEMENT_NODE = 1;
215var DOM_ATTRIBUTE_NODE = 2;
216var DOM_TEXT_NODE = 3;
217var DOM_CDATA_SECTION_NODE = 4;
218var DOM_ENTITY_REFERENCE_NODE = 5;
219var DOM_ENTITY_NODE = 6;
220var DOM_PROCESSING_INSTRUCTION_NODE = 7;
221var DOM_COMMENT_NODE = 8;
222var DOM_DOCUMENT_NODE = 9;
223var DOM_DOCUMENT_TYPE_NODE = 10;
224var DOM_DOCUMENT_FRAGMENT_NODE = 11;
225var DOM_NOTATION_NODE = 12;
226
227// Traverses the element nodes in the DOM section underneath the given
228// node and invokes the given callbacks as methods on every element
229// node encountered. Function opt_pre is invoked before a node's
230// children are traversed; opt_post is invoked after they are
231// traversed. Traversal will not be continued if a callback function
232// returns boolean false. NOTE(mesch): copied from
233// <//google3/maps/webmaps/javascript/dom.js>.
234function domTraverseElements(node, opt_pre, opt_post) {
235  var ret;
236  if (opt_pre) {
237    ret = opt_pre.call(null, node);
238    if (typeof ret == 'boolean' && !ret) {
239      return false;
240    }
241  }
242
243  for (var c = node.firstChild; c; c = c.nextSibling) {
244    if (c.nodeType == DOM_ELEMENT_NODE) {
245      ret = arguments.callee.call(this, c, opt_pre, opt_post);
246      if (typeof ret == 'boolean' && !ret) {
247        return false;
248      }
249    }
250  }
251
252  if (opt_post) {
253    ret = opt_post.call(null, node);
254    if (typeof ret == 'boolean' && !ret) {
255      return false;
256    }
257  }
258}
259
260// Our W3C DOM Node implementation. Note we call it XNode because we
261// can't define the identifier Node. We do this mostly for Opera,
262// where we can't reuse the HTML DOM for parsing our own XML, and for
263// Safari, where it is too expensive to have the template processor
264// operate on native DOM nodes.
265function XNode(type, name, opt_value, opt_owner) {
266  this.attributes = [];
267  this.childNodes = [];
268
269  XNode.init.call(this, type, name, opt_value, opt_owner);
270}
271
272// Don't call as method, use apply() or call().
273XNode.init = function(type, name, value, owner) {
274  this.nodeType = type - 0;
275  this.nodeName = '' + name;
276  this.nodeValue = '' + value;
277  this.ownerDocument = owner;
278
279  this.firstChild = null;
280  this.lastChild = null;
281  this.nextSibling = null;
282  this.previousSibling = null;
283  this.parentNode = null;
284}
285
286XNode.unused_ = [];
287
288XNode.recycle = function(node) {
289  if (!node) {
290    return;
291  }
292
293  if (node.constructor == XDocument) {
294    XNode.recycle(node.documentElement);
295    return;
296  }
297
298  if (node.constructor != this) {
299    return;
300  }
301
302  XNode.unused_.push(node);
303  for (var a = 0; a < node.attributes.length; ++a) {
304    XNode.recycle(node.attributes[a]);
305  }
306  for (var c = 0; c < node.childNodes.length; ++c) {
307    XNode.recycle(node.childNodes[c]);
308  }
309  node.attributes.length = 0;
310  node.childNodes.length = 0;
311  XNode.init.call(node, 0, '', '', null);
312}
313
314XNode.create = function(type, name, value, owner) {
315  if (XNode.unused_.length > 0) {
316    var node = XNode.unused_.pop();
317    XNode.init.call(node, type, name, value, owner);
318    return node;
319  } else {
320    return new XNode(type, name, value, owner);
321  }
322}
323
324XNode.prototype.appendChild = function(node) {
325  // firstChild
326  if (this.childNodes.length == 0) {
327    this.firstChild = node;
328  }
329
330  // previousSibling
331  node.previousSibling = this.lastChild;
332
333  // nextSibling
334  node.nextSibling = null;
335  if (this.lastChild) {
336    this.lastChild.nextSibling = node;
337  }
338
339  // parentNode
340  node.parentNode = this;
341
342  // lastChild
343  this.lastChild = node;
344
345  // childNodes
346  this.childNodes.push(node);
347}
348
349
350XNode.prototype.replaceChild = function(newNode, oldNode) {
351  if (oldNode == newNode) {
352    return;
353  }
354
355  for (var i = 0; i < this.childNodes.length; ++i) {
356    if (this.childNodes[i] == oldNode) {
357      this.childNodes[i] = newNode;
358
359      var p = oldNode.parentNode;
360      oldNode.parentNode = null;
361      newNode.parentNode = p;
362
363      p = oldNode.previousSibling;
364      oldNode.previousSibling = null;
365      newNode.previousSibling = p;
366      if (newNode.previousSibling) {
367        newNode.previousSibling.nextSibling = newNode;
368      }
369
370      p = oldNode.nextSibling;
371      oldNode.nextSibling = null;
372      newNode.nextSibling = p;
373      if (newNode.nextSibling) {
374        newNode.nextSibling.previousSibling = newNode;
375      }
376
377      if (this.firstChild == oldNode) {
378        this.firstChild = newNode;
379      }
380
381      if (this.lastChild == oldNode) {
382        this.lastChild = newNode;
383      }
384
385      break;
386    }
387  }
388}
389
390
391XNode.prototype.insertBefore = function(newNode, oldNode) {
392  if (oldNode == newNode) {
393    return;
394  }
395
396  if (oldNode.parentNode != this) {
397    return;
398  }
399
400  if (newNode.parentNode) {
401    newNode.parentNode.removeChild(newNode);
402  }
403
404  var newChildren = [];
405  for (var i = 0; i < this.childNodes.length; ++i) {
406    var c = this.childNodes[i];
407    if (c == oldNode) {
408      newChildren.push(newNode);
409
410      newNode.parentNode = this;
411
412      newNode.previousSibling = oldNode.previousSibling;
413      oldNode.previousSibling = newNode;
414      if (newNode.previousSibling) {
415        newNode.previousSibling.nextSibling = newNode;
416      }
417
418      newNode.nextSibling = oldNode;
419
420      if (this.firstChild == oldNode) {
421        this.firstChild = newNode;
422      }
423    }
424    newChildren.push(c);
425  }
426  this.childNodes = newChildren;
427}
428
429
430XNode.prototype.removeChild = function(node) {
431  var newChildren = [];
432  for (var i = 0; i < this.childNodes.length; ++i) {
433    var c = this.childNodes[i];
434    if (c != node) {
435      newChildren.push(c);
436    } else {
437      if (c.previousSibling) {
438        c.previousSibling.nextSibling = c.nextSibling;
439      }
440      if (c.nextSibling) {
441        c.nextSibling.previousSibling = c.previousSibling;
442      }
443      if (this.firstChild == c) {
444        this.firstChild = c.nextSibling;
445      }
446      if (this.lastChild == c) {
447        this.lastChild = c.previousSibling;
448      }
449    }
450  }
451  this.childNodes = newChildren;
452}
453
454
455XNode.prototype.hasAttributes = function() {
456  return this.attributes.length > 0;
457}
458
459
460XNode.prototype.setAttribute = function(name, value) {
461  for (var i = 0; i < this.attributes.length; ++i) {
462    if (this.attributes[i].nodeName == name) {
463      this.attributes[i].nodeValue = '' + value;
464      return;
465    }
466  }
467  this.attributes.push(XNode.create(DOM_ATTRIBUTE_NODE, name, value, this));
468}
469
470
471XNode.prototype.getAttribute = function(name) {
472  for (var i = 0; i < this.attributes.length; ++i) {
473    if (this.attributes[i].nodeName == name) {
474      return this.attributes[i].nodeValue;
475    }
476  }
477  return null;
478}
479
480
481XNode.prototype.removeAttribute = function(name) {
482  var a = [];
483  for (var i = 0; i < this.attributes.length; ++i) {
484    if (this.attributes[i].nodeName != name) {
485      a.push(this.attributes[i]);
486    }
487  }
488  this.attributes = a;
489}
490
491
492XNode.prototype.getElementsByTagName = function(name) {
493  var ret = [];
494  var self = this;
495  if ("*" == name) {
496    domTraverseElements(this, function(node) {
497      if (self == node) return;
498      ret.push(node);
499    }, null);
500  } else {
501    domTraverseElements(this, function(node) {
502      if (self == node) return;
503      if (node.nodeName == name) {
504        ret.push(node);
505      }
506    }, null);
507  }
508  return ret;
509}
510
511
512XNode.prototype.getElementById = function(id) {
513  var ret = null;
514  domTraverseElements(this, function(node) {
515    if (node.getAttribute('id') == id) {
516      ret = node;
517      return false;
518    }
519  }, null);
520  return ret;
521}
522
523
524function XDocument() {
525  // NOTE(mesch): Acocording to the DOM Spec, ownerDocument of a
526  // document node is null.
527  XNode.call(this, DOM_DOCUMENT_NODE, '#document', null, null);
528  this.documentElement = null;
529}
530
531XDocument.prototype = new XNode(DOM_DOCUMENT_NODE, '#document');
532
533XDocument.prototype.clear = function() {
534  XNode.recycle(this.documentElement);
535  this.documentElement = null;
536}
537
538XDocument.prototype.appendChild = function(node) {
539  XNode.prototype.appendChild.call(this, node);
540  this.documentElement = this.childNodes[0];
541}
542
543XDocument.prototype.createElement = function(name) {
544  return XNode.create(DOM_ELEMENT_NODE, name, null, this);
545}
546
547XDocument.prototype.createDocumentFragment = function() {
548  return XNode.create(DOM_DOCUMENT_FRAGMENT_NODE, '#document-fragment',
549                    null, this);
550}
551
552XDocument.prototype.createTextNode = function(value) {
553  return XNode.create(DOM_TEXT_NODE, '#text', value, this);
554}
555
556XDocument.prototype.createAttribute = function(name) {
557  return XNode.create(DOM_ATTRIBUTE_NODE, name, null, this);
558}
559
560XDocument.prototype.createComment = function(data) {
561  return XNode.create(DOM_COMMENT_NODE, '#comment', data, this);
562}
563
564XDocument.prototype.createCDATASection = function(data) {
565  return XNode.create(DOM_CDATA_SECTION_NODE, '#cdata-section', data, this);
566}