not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

242 lines
6.0 KiB

  1. /**
  2. * Simple XML DOM implementation based on sax that works with Strings.
  3. *
  4. * If you have an XML string and want a DOM this utility is convenient.
  5. *
  6. * var domjs = new DomJS();
  7. * domjs.parse(xmlString, function(err, dom) {
  8. *
  9. * });
  10. *
  11. * If you want to compile C there are versions based on libxml2
  12. * and jsdom is full featured but complicated.
  13. *
  14. * This is "lightweight" meaning simple and serves my purpose, it does not support namespaces or all
  15. * of the features of XML 1.0 it just takes a string and returns a JavaScript object graph.
  16. *
  17. * There are only 5 types of object supported
  18. * Element, Text, ProcessingInstruction, CDATASection and Comment.
  19. *
  20. * e.g.
  21. *
  22. * take <xml><elem att="val1"/><elem att="val1"/><elem att="val1"/></xml>
  23. *
  24. * return { name : "xml",
  25. * attributes : {}
  26. * children [
  27. * { name : "elem", attributes : {att:'val1'}, children [] },
  28. * { name : "elem", attributes : {att:'val1'}, children [] },
  29. * { name : "elem", attributes : {att:'val1'}, children [] }
  30. * ]
  31. * }
  32. *
  33. * The object returned is an instance of Element and can be serialized back out with obj.toXml();
  34. *
  35. *
  36. * @constructor DomJS
  37. */
  38. var DomJS = function() {
  39. /**
  40. * The root element of the XML document currently being parsed.
  41. */
  42. this.root = null;
  43. this.stack = new Array();
  44. this.currElement = null;
  45. /**
  46. * Flag that is set to true if there was a Sax error parsing the XML.
  47. */
  48. this.error = false;
  49. /**
  50. * Use strict parsing, this value is passed to the sax parser.
  51. */
  52. this.strict = true;
  53. /**
  54. * Set to true to parse and write ProcessingInstructions
  55. * By default false for backwards comatability
  56. */
  57. this.parseProcessingInstructions = false;
  58. // undefined by default
  59. // this.processingInstructions = new Array();
  60. };
  61. DomJS.prototype.parse = function(string, cb) {
  62. if (typeof string != 'string') {
  63. cb(true, 'Data is not a string');
  64. return;
  65. }
  66. var self = this;
  67. var parser = sax.parser(this.strict);
  68. parser.onerror = function (err) {
  69. self.error = true;
  70. cb(true, err);
  71. };
  72. parser.ontext = function (text) {
  73. if (self.currElement == null) {
  74. // console.log("Content in the prolog " + text);
  75. return;
  76. }
  77. var textNode = new Text(text);
  78. self.currElement.children.push(textNode);
  79. };
  80. parser.onopencdata = function () {
  81. var cdataNode = new CDATASection();
  82. self.currElement.children.push(cdataNode);
  83. };
  84. parser.oncdata = function (data) {
  85. var cdataNode = self.currElement.children[self.currElement.children.length - 1];
  86. cdataNode.appendData(data);
  87. };
  88. // do nothing on parser.onclosecdata
  89. parser.onopentag = function (node) {
  90. var elem = new Element(node.name, node.attributes);
  91. if (self.root == null) {
  92. self.root = elem;
  93. if ( self.processingInstructions ) {
  94. elem.processingInstructions = self.processingInstructions;
  95. }
  96. }
  97. if (self.currElement != null) {
  98. self.currElement.children.push(elem);
  99. }
  100. self.currElement = elem;
  101. self.stack.push(self.currElement);
  102. };
  103. parser.onclosetag = function (node) {
  104. self.stack.pop();
  105. self.currElement = self.stack[self.stack.length - 1 ];// self.stack.peek();
  106. };
  107. parser.oncomment = function (comment) {
  108. if (self.currElement == null) {
  109. //console.log("Comments in the prolog discarded " + comment);
  110. return;
  111. }
  112. var commentNode = new Comment(comment);
  113. self.currElement.children.push(commentNode);
  114. };
  115. parser.onprocessinginstruction = function (node) {
  116. if (self.parseProcessingInstructions === true) {
  117. if ( self.processingInstructions === undefined) {
  118. self.processingInstructions = new Array();
  119. }
  120. var pi = new ProcessingInstruction(node.name, node.body);
  121. self.processingInstructions.push(pi);
  122. }
  123. };
  124. parser.onend = function () {
  125. if ( self.error == false) {
  126. cb(false, self.root);
  127. }
  128. };
  129. parser.write(string).close();
  130. };
  131. DomJS.prototype.reset = function() {
  132. this.root = null;
  133. this.stack = new Array();
  134. this.currElement = null;
  135. this.error = false;
  136. };
  137. var escape = function(string) {
  138. return string.replace(/&/g, '&amp;')
  139. .replace(/>/g, '&gt;')
  140. .replace(/</g, '&lt;')
  141. .replace(/"/g, '&quot;')
  142. .replace(/'/g, '&apos;');
  143. };
  144. /**
  145. *
  146. * @constructor Element
  147. */
  148. var Element = function(name, attributes, children ) {
  149. this.name = name;
  150. this.attributes = attributes || [];
  151. this.children = children || [];
  152. // undefined by default
  153. // this.processingInstructions = new Array();
  154. };
  155. Element.prototype.toXml = function(sb) {
  156. if (typeof sb == 'undefined') {
  157. sb = {buf:''}; // Strings are pass by value in JS it seems
  158. }
  159. if (this.processingInstructions) {
  160. for (var i = 0 ; i < this.processingInstructions.length ; i++) {
  161. sb.buf += '<?' + this.processingInstructions[i].name + ' ' + this.processingInstructions[i].body + '?>\n';
  162. }
  163. }
  164. sb.buf += '<' + this.name;
  165. for (att in this.attributes) {
  166. sb.buf += ' ' + att + '="' + escape(this.attributes[att]) + '"';
  167. }
  168. if (this.children.length != 0) {
  169. sb.buf += '>';
  170. for (var i = 0 ; i < this.children.length ; i++) {
  171. this.children[i].toXml(sb);
  172. }
  173. sb.buf += '</' + this.name + '>';
  174. }
  175. else {
  176. sb.buf += '/>';
  177. }
  178. return sb.buf;
  179. };
  180. Element.prototype.firstChild = function() {
  181. if ( this.children.length > 0) {
  182. return this.children[0];
  183. }
  184. return null;
  185. };
  186. Element.prototype.text = function() {
  187. if ( this.children.length > 0) {
  188. if (typeof this.children[0].text == 'string') {
  189. return this.children[0].text;
  190. };
  191. }
  192. return null;
  193. };
  194. var Text = function(data){
  195. this.text = data;
  196. };
  197. Text.prototype.toXml = function(sb) {
  198. sb.buf += escape(this.text);
  199. };
  200. var Comment = function(comment) {
  201. this.comment = comment;
  202. };
  203. Comment.prototype.toXml = function(sb) {
  204. sb.buf += '<!--' + this.comment + '-->';
  205. };
  206. var ProcessingInstruction = function(name, body) {
  207. this.name = name;
  208. this.body = body;
  209. };
  210. ProcessingInstruction.prototype.toXml = function(sb) {
  211. sb.buf += '<?' + this.name + ' ' + this.body + '?>';
  212. };
  213. var CDATASection = function(data){
  214. this.text = data || '';
  215. };
  216. CDATASection.prototype.toXml = function(sb) {
  217. sb.buf += '<![CDATA[' + this.text + ']]>';
  218. };
  219. CDATASection.prototype.appendData = function(data) {
  220. this.text += data;
  221. };