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