项目原始demo,不改动
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
このリポジトリはアーカイブされています。 ファイルの閲覧とクローンは可能ですが、プッシュや、課題・プルリクエストのオープンはできません。
 
 
 
 

315 行
7.3 KiB

  1. var Tokenizer = require("./Tokenizer.js");
  2. /*
  3. Options:
  4. xmlMode: Special behavior for script/style tags (true by default)
  5. lowerCaseAttributeNames: call .toLowerCase for each attribute name (true if xmlMode is `false`)
  6. lowerCaseTags: call .toLowerCase for each tag name (true if xmlMode is `false`)
  7. */
  8. /*
  9. Callbacks:
  10. oncdataend,
  11. oncdatastart,
  12. onclosetag,
  13. oncomment,
  14. oncommentend,
  15. onerror,
  16. onopentag,
  17. onprocessinginstruction,
  18. onreset,
  19. ontext
  20. */
  21. var formTags = {
  22. input: true,
  23. option: true,
  24. optgroup: true,
  25. select: true,
  26. button: true,
  27. datalist: true,
  28. textarea: true
  29. };
  30. var openImpliesClose = {
  31. tr : { tr:true, th:true, td:true },
  32. th : { th:true },
  33. td : { thead:true, td:true },
  34. body : { head:true, link:true, script:true },
  35. li : { li:true },
  36. p : { p:true },
  37. select : formTags,
  38. input : formTags,
  39. output : formTags,
  40. button : formTags,
  41. datalist: formTags,
  42. textarea: formTags,
  43. option : { option:true },
  44. optgroup: { optgroup:true }
  45. };
  46. var voidElements = {
  47. __proto__: null,
  48. area: true,
  49. base: true,
  50. basefont: true,
  51. br: true,
  52. col: true,
  53. command: true,
  54. embed: true,
  55. frame: true,
  56. hr: true,
  57. img: true,
  58. input: true,
  59. isindex: true,
  60. keygen: true,
  61. link: true,
  62. meta: true,
  63. param: true,
  64. source: true,
  65. track: true,
  66. wbr: true
  67. };
  68. var re_nameEnd = /\s|\//;
  69. function Parser(cbs, options){
  70. this._options = options || {};
  71. this._cbs = cbs || {};
  72. this._tagname = "";
  73. this._attribname = "";
  74. this._attribvalue = "";
  75. this._attribs = null;
  76. this._stack = [];
  77. this._done = false;
  78. this.startIndex = 0;
  79. this.endIndex = null;
  80. this._tokenizer = new Tokenizer(options, this);
  81. }
  82. require("util").inherits(Parser, require("events").EventEmitter);
  83. Parser.prototype._updatePosition = function(initialOffset){
  84. if(this.endIndex === null){
  85. this.startIndex = this._tokenizer._sectionStart <= initialOffset ? 0 : this._tokenizer._sectionStart - initialOffset;
  86. }
  87. this.startIndex = this.endIndex + 1;
  88. this.endIndex = this._tokenizer._index;
  89. };
  90. //Tokenizer event handlers
  91. Parser.prototype.ontext = function(data){
  92. this._updatePosition(1);
  93. this.endIndex--;
  94. if(this._cbs.ontext) this._cbs.ontext(data);
  95. };
  96. Parser.prototype.onopentagname = function(name){
  97. if(!(this._options.xmlMode || "lowerCaseTags" in this._options) || this._options.lowerCaseTags){
  98. name = name.toLowerCase();
  99. }
  100. this._tagname = name;
  101. if (!this._options.xmlMode && name in openImpliesClose) {
  102. for(
  103. var el;
  104. (el = this._stack[this._stack.length-1]) in openImpliesClose[name];
  105. this.onclosetag(el)
  106. );
  107. }
  108. if(this._options.xmlMode || !(name in voidElements)){
  109. this._stack.push(name);
  110. }
  111. if(this._cbs.onopentagname) this._cbs.onopentagname(name);
  112. if(this._cbs.onopentag) this._attribs = {};
  113. };
  114. Parser.prototype.onopentagend = function(){
  115. this._updatePosition(1);
  116. if(this._attribs){
  117. if(this._cbs.onopentag) this._cbs.onopentag(this._tagname, this._attribs);
  118. this._attribs = null;
  119. }
  120. if(!this._options.xmlMode && this._cbs.onclosetag && this._tagname in voidElements){
  121. this._cbs.onclosetag(this._tagname);
  122. }
  123. this._tagname = "";
  124. };
  125. Parser.prototype.onclosetag = function(name){
  126. this._updatePosition(1);
  127. if(!(this._options.xmlMode || "lowerCaseTags" in this._options) || this._options.lowerCaseTags){
  128. name = name.toLowerCase();
  129. }
  130. if(this._stack.length && (!(name in voidElements) || this._options.xmlMode)){
  131. var pos = this._stack.lastIndexOf(name);
  132. if(pos !== -1){
  133. if(this._cbs.onclosetag){
  134. pos = this._stack.length - pos;
  135. while(pos--) this._cbs.onclosetag(this._stack.pop());
  136. }
  137. else this._stack.length = pos;
  138. } else if(name === "p" && !this._options.xmlMode){
  139. this.onopentagname(name);
  140. this._closeCurrentTag();
  141. }
  142. } else if(!this._options.xmlMode && (name === "br" || name === "p")){
  143. this.onopentagname(name);
  144. this._closeCurrentTag();
  145. }
  146. };
  147. Parser.prototype.onselfclosingtag = function(){
  148. if(this._options.xmlMode){
  149. this._closeCurrentTag();
  150. } else {
  151. this.onopentagend();
  152. }
  153. };
  154. Parser.prototype._closeCurrentTag = function(){
  155. var name = this._tagname;
  156. this.onopentagend();
  157. //self-closing tags will be on the top of the stack
  158. //(cheaper check than in onclosetag)
  159. if(this._stack[this._stack.length-1] === name){
  160. if(this._cbs.onclosetag){
  161. this._cbs.onclosetag(name);
  162. }
  163. this._stack.pop();
  164. }
  165. };
  166. Parser.prototype.onattribname = function(name){
  167. if(!(this._options.xmlMode || "lowerCaseAttributeNames" in this._options) || this._options.lowerCaseAttributeNames){
  168. name = name.toLowerCase();
  169. }
  170. this._attribname = name;
  171. };
  172. Parser.prototype.onattribdata = function(value){
  173. this._attribvalue += value;
  174. };
  175. Parser.prototype.onattribend = function(){
  176. if(this._cbs.onattribute) this._cbs.onattribute(this._attribname, this._attribvalue);
  177. if(
  178. this._attribs &&
  179. !Object.prototype.hasOwnProperty.call(this._attribs, this._attribname)
  180. ){
  181. this._attribs[this._attribname] = this._attribvalue;
  182. }
  183. this._attribname = "";
  184. this._attribvalue = "";
  185. };
  186. Parser.prototype.ondeclaration = function(value){
  187. if(this._cbs.onprocessinginstruction){
  188. var idx = value.search(re_nameEnd),
  189. name = idx < 0 ? value : value.substr(0, idx);
  190. if(!(this._options.xmlMode || "lowerCaseTags" in this._options) || this._options.lowerCaseTags){
  191. name = name.toLowerCase();
  192. }
  193. this._cbs.onprocessinginstruction("!" + name, "!" + value);
  194. }
  195. };
  196. Parser.prototype.onprocessinginstruction = function(value){
  197. if(this._cbs.onprocessinginstruction){
  198. var idx = value.search(re_nameEnd),
  199. name = idx < 0 ? value : value.substr(0, idx);
  200. if(!(this._options.xmlMode || "lowerCaseTags" in this._options) || this._options.lowerCaseTags){
  201. name = name.toLowerCase();
  202. }
  203. this._cbs.onprocessinginstruction("?" + name, "?" + value);
  204. }
  205. };
  206. Parser.prototype.oncomment = function(value){
  207. this._updatePosition(4);
  208. if(this._cbs.oncomment) this._cbs.oncomment(value);
  209. if(this._cbs.oncommentend) this._cbs.oncommentend();
  210. };
  211. Parser.prototype.oncdata = function(value){
  212. this._updatePosition(1);
  213. if(this._options.xmlMode){
  214. if(this._cbs.oncdatastart) this._cbs.oncdatastart();
  215. if(this._cbs.ontext) this._cbs.ontext(value);
  216. if(this._cbs.oncdataend) this._cbs.oncdataend();
  217. } else {
  218. this.oncomment("[CDATA[" + value + "]]");
  219. }
  220. };
  221. Parser.prototype.onerror = function(err){
  222. if(this._cbs.onerror) this._cbs.onerror(err);
  223. };
  224. Parser.prototype.onend = function(){
  225. if(this._cbs.onclosetag){
  226. for(
  227. var i = this._stack.length;
  228. i > 0;
  229. this._cbs.onclosetag(this._stack[--i])
  230. );
  231. }
  232. if(this._cbs.onend) this._cbs.onend();
  233. };
  234. //Resets the parser to a blank state, ready to parse a new HTML document
  235. Parser.prototype.reset = function(){
  236. if(this._cbs.onreset) this._cbs.onreset();
  237. this._tokenizer.reset();
  238. this._tagname = "";
  239. this._attribname = "";
  240. this._attribs = null;
  241. this._stack = [];
  242. this._done = false;
  243. };
  244. //Parses a complete HTML document and pushes it to the handler
  245. Parser.prototype.parseComplete = function(data){
  246. this.reset();
  247. this.end(data);
  248. };
  249. Parser.prototype.write = function(chunk){
  250. if(this._done) this.onerror(Error(".write() after done!"));
  251. this._tokenizer.write(chunk);
  252. };
  253. Parser.prototype.end = function(chunk){
  254. if(this._done) this.onerror(Error(".end() after done!"));
  255. this._tokenizer.end(chunk);
  256. this._done = true;
  257. };
  258. //alias for backwards compat
  259. Parser.prototype.parseChunk = Parser.prototype.write;
  260. Parser.prototype.done = Parser.prototype.end;
  261. module.exports = Parser;