index.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  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. };
  27. DomHandler.prototype.onparserinit = function(parser){
  28. this._parser = parser;
  29. };
  30. //Resets the handler back to starting state
  31. DomHandler.prototype.onreset = function(){
  32. DomHandler.call(this, this._callback, this._options, this._elementCB);
  33. };
  34. //Signals the handler that parsing is done
  35. DomHandler.prototype.onend = function(){
  36. if(this._done) return;
  37. this._done = true;
  38. this._parser = null;
  39. this._handleCallback(null);
  40. };
  41. DomHandler.prototype._handleCallback =
  42. DomHandler.prototype.onerror = function(error){
  43. if(typeof this._callback === "function"){
  44. this._callback(error, this.dom);
  45. } else {
  46. if(error) throw error;
  47. }
  48. };
  49. DomHandler.prototype.onclosetag = function(){
  50. //if(this._tagStack.pop().name !== name) this._handleCallback(Error("Tagname didn't match!"));
  51. var elem = this._tagStack.pop();
  52. if(this._elementCB) this._elementCB(elem);
  53. };
  54. DomHandler.prototype._addDomElement = function(element){
  55. var parent = this._tagStack[this._tagStack.length - 1];
  56. var siblings = parent ? parent.children : this.dom;
  57. var previousSibling = siblings[siblings.length - 1];
  58. element.next = null;
  59. if(this._options.withStartIndices){
  60. element.startIndex = this._parser.startIndex;
  61. }
  62. if (this._options.withDomLvl1) {
  63. element.__proto__ = element.type === "tag" ? ElementPrototype : NodePrototype;
  64. }
  65. if(previousSibling){
  66. element.prev = previousSibling;
  67. previousSibling.next = element;
  68. } else {
  69. element.prev = null;
  70. }
  71. siblings.push(element);
  72. element.parent = parent || null;
  73. };
  74. DomHandler.prototype.onopentag = function(name, attribs){
  75. var element = {
  76. type: name === "script" ? ElementType.Script : name === "style" ? ElementType.Style : ElementType.Tag,
  77. name: name,
  78. attribs: attribs,
  79. children: []
  80. };
  81. this._addDomElement(element);
  82. this._tagStack.push(element);
  83. };
  84. DomHandler.prototype.ontext = function(data){
  85. //the ignoreWhitespace is officially dropped, but for now,
  86. //it's an alias for normalizeWhitespace
  87. var normalize = this._options.normalizeWhitespace || this._options.ignoreWhitespace;
  88. var lastTag;
  89. if(!this._tagStack.length && this.dom.length && (lastTag = this.dom[this.dom.length-1]).type === ElementType.Text){
  90. if(normalize){
  91. lastTag.data = (lastTag.data + data).replace(re_whitespace, " ");
  92. } else {
  93. lastTag.data += data;
  94. }
  95. } else {
  96. if(
  97. this._tagStack.length &&
  98. (lastTag = this._tagStack[this._tagStack.length - 1]) &&
  99. (lastTag = lastTag.children[lastTag.children.length - 1]) &&
  100. lastTag.type === ElementType.Text
  101. ){
  102. if(normalize){
  103. lastTag.data = (lastTag.data + data).replace(re_whitespace, " ");
  104. } else {
  105. lastTag.data += data;
  106. }
  107. } else {
  108. if(normalize){
  109. data = data.replace(re_whitespace, " ");
  110. }
  111. this._addDomElement({
  112. data: data,
  113. type: ElementType.Text
  114. });
  115. }
  116. }
  117. };
  118. DomHandler.prototype.oncomment = function(data){
  119. var lastTag = this._tagStack[this._tagStack.length - 1];
  120. if(lastTag && lastTag.type === ElementType.Comment){
  121. lastTag.data += data;
  122. return;
  123. }
  124. var element = {
  125. data: data,
  126. type: ElementType.Comment
  127. };
  128. this._addDomElement(element);
  129. this._tagStack.push(element);
  130. };
  131. DomHandler.prototype.oncdatastart = function(){
  132. var element = {
  133. children: [{
  134. data: "",
  135. type: ElementType.Text
  136. }],
  137. type: ElementType.CDATA
  138. };
  139. this._addDomElement(element);
  140. this._tagStack.push(element);
  141. };
  142. DomHandler.prototype.oncommentend = DomHandler.prototype.oncdataend = function(){
  143. this._tagStack.pop();
  144. };
  145. DomHandler.prototype.onprocessinginstruction = function(name, data){
  146. this._addDomElement({
  147. name: name,
  148. data: data,
  149. type: ElementType.Directive
  150. });
  151. };
  152. module.exports = DomHandler;