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.

index.js 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. var ElementType = require("domelementtype");
  2. var re_whitespace = /\s+/g;
  3. var NodePrototype = require("./lib/node");
  4. var ElementPrototype = require("./lib/element");
  5. function DomHandler(callback, options, elementCB){
  6. if(typeof callback === "object"){
  7. elementCB = options;
  8. options = callback;
  9. callback = null;
  10. } else if(typeof options === "function"){
  11. elementCB = options;
  12. options = defaultOpts;
  13. }
  14. this._callback = callback;
  15. this._options = options || defaultOpts;
  16. this._elementCB = elementCB;
  17. this.dom = [];
  18. this._done = false;
  19. this._tagStack = [];
  20. this._parser = this._parser || null;
  21. }
  22. //default options
  23. var defaultOpts = {
  24. normalizeWhitespace: false, //Replace all whitespace with single spaces
  25. withStartIndices: false, //Add startIndex properties to nodes
  26. withEndIndices: false, //Add endIndex properties to nodes
  27. };
  28. DomHandler.prototype.onparserinit = function(parser){
  29. this._parser = parser;
  30. };
  31. //Resets the handler back to starting state
  32. DomHandler.prototype.onreset = function(){
  33. DomHandler.call(this, this._callback, this._options, this._elementCB);
  34. };
  35. //Signals the handler that parsing is done
  36. DomHandler.prototype.onend = function(){
  37. if(this._done) return;
  38. this._done = true;
  39. this._parser = null;
  40. this._handleCallback(null);
  41. };
  42. DomHandler.prototype._handleCallback =
  43. DomHandler.prototype.onerror = function(error){
  44. if(typeof this._callback === "function"){
  45. this._callback(error, this.dom);
  46. } else {
  47. if(error) throw error;
  48. }
  49. };
  50. DomHandler.prototype.onclosetag = function(){
  51. //if(this._tagStack.pop().name !== name) this._handleCallback(Error("Tagname didn't match!"));
  52. var elem = this._tagStack.pop();
  53. if(this._options.withEndIndices && elem){
  54. elem.endIndex = this._parser.endIndex;
  55. }
  56. if(this._elementCB) this._elementCB(elem);
  57. };
  58. DomHandler.prototype._createDomElement = function(properties){
  59. if (!this._options.withDomLvl1) return properties;
  60. var element;
  61. if (properties.type === "tag") {
  62. element = Object.create(ElementPrototype);
  63. } else {
  64. element = Object.create(NodePrototype);
  65. }
  66. for (var key in properties) {
  67. if (properties.hasOwnProperty(key)) {
  68. element[key] = properties[key];
  69. }
  70. }
  71. return element;
  72. };
  73. DomHandler.prototype._addDomElement = function(element){
  74. var parent = this._tagStack[this._tagStack.length - 1];
  75. var siblings = parent ? parent.children : this.dom;
  76. var previousSibling = siblings[siblings.length - 1];
  77. element.next = null;
  78. if(this._options.withStartIndices){
  79. element.startIndex = this._parser.startIndex;
  80. }
  81. if(this._options.withEndIndices){
  82. element.endIndex = this._parser.endIndex;
  83. }
  84. if(previousSibling){
  85. element.prev = previousSibling;
  86. previousSibling.next = element;
  87. } else {
  88. element.prev = null;
  89. }
  90. siblings.push(element);
  91. element.parent = parent || null;
  92. };
  93. DomHandler.prototype.onopentag = function(name, attribs){
  94. var properties = {
  95. type: name === "script" ? ElementType.Script : name === "style" ? ElementType.Style : ElementType.Tag,
  96. name: name,
  97. attribs: attribs,
  98. children: []
  99. };
  100. var element = this._createDomElement(properties);
  101. this._addDomElement(element);
  102. this._tagStack.push(element);
  103. };
  104. DomHandler.prototype.ontext = function(data){
  105. //the ignoreWhitespace is officially dropped, but for now,
  106. //it's an alias for normalizeWhitespace
  107. var normalize = this._options.normalizeWhitespace || this._options.ignoreWhitespace;
  108. var lastTag;
  109. if(!this._tagStack.length && this.dom.length && (lastTag = this.dom[this.dom.length-1]).type === ElementType.Text){
  110. if(normalize){
  111. lastTag.data = (lastTag.data + data).replace(re_whitespace, " ");
  112. } else {
  113. lastTag.data += data;
  114. }
  115. } else {
  116. if(
  117. this._tagStack.length &&
  118. (lastTag = this._tagStack[this._tagStack.length - 1]) &&
  119. (lastTag = lastTag.children[lastTag.children.length - 1]) &&
  120. lastTag.type === ElementType.Text
  121. ){
  122. if(normalize){
  123. lastTag.data = (lastTag.data + data).replace(re_whitespace, " ");
  124. } else {
  125. lastTag.data += data;
  126. }
  127. } else {
  128. if(normalize){
  129. data = data.replace(re_whitespace, " ");
  130. }
  131. var element = this._createDomElement({
  132. data: data,
  133. type: ElementType.Text
  134. });
  135. this._addDomElement(element);
  136. }
  137. }
  138. };
  139. DomHandler.prototype.oncomment = function(data){
  140. var lastTag = this._tagStack[this._tagStack.length - 1];
  141. if(lastTag && lastTag.type === ElementType.Comment){
  142. lastTag.data += data;
  143. return;
  144. }
  145. var properties = {
  146. data: data,
  147. type: ElementType.Comment
  148. };
  149. var element = this._createDomElement(properties);
  150. this._addDomElement(element);
  151. this._tagStack.push(element);
  152. };
  153. DomHandler.prototype.oncdatastart = function(){
  154. var properties = {
  155. children: [{
  156. data: "",
  157. type: ElementType.Text
  158. }],
  159. type: ElementType.CDATA
  160. };
  161. var element = this._createDomElement(properties);
  162. this._addDomElement(element);
  163. this._tagStack.push(element);
  164. };
  165. DomHandler.prototype.oncommentend = DomHandler.prototype.oncdataend = function(){
  166. this._tagStack.pop();
  167. };
  168. DomHandler.prototype.onprocessinginstruction = function(name, data){
  169. var element = this._createDomElement({
  170. name: name,
  171. data: data,
  172. type: ElementType.Directive
  173. });
  174. this._addDomElement(element);
  175. };
  176. module.exports = DomHandler;