htmlparser2.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. 'use strict';
  2. var doctype = require('../common/doctype'),
  3. DOCUMENT_MODE = require('../common/html').DOCUMENT_MODE;
  4. //Conversion tables for DOM Level1 structure emulation
  5. var nodeTypes = {
  6. element: 1,
  7. text: 3,
  8. cdata: 4,
  9. comment: 8
  10. };
  11. var nodePropertyShorthands = {
  12. tagName: 'name',
  13. childNodes: 'children',
  14. parentNode: 'parent',
  15. previousSibling: 'prev',
  16. nextSibling: 'next',
  17. nodeValue: 'data'
  18. };
  19. //Node
  20. var Node = function (props) {
  21. for (var key in props) {
  22. if (props.hasOwnProperty(key))
  23. this[key] = props[key];
  24. }
  25. };
  26. Node.prototype = {
  27. get firstChild() {
  28. var children = this.children;
  29. return children && children[0] || null;
  30. },
  31. get lastChild() {
  32. var children = this.children;
  33. return children && children[children.length - 1] || null;
  34. },
  35. get nodeType() {
  36. return nodeTypes[this.type] || nodeTypes.element;
  37. }
  38. };
  39. Object.keys(nodePropertyShorthands).forEach(function (key) {
  40. var shorthand = nodePropertyShorthands[key];
  41. Object.defineProperty(Node.prototype, key, {
  42. get: function () {
  43. return this[shorthand] || null;
  44. },
  45. set: function (val) {
  46. this[shorthand] = val;
  47. return val;
  48. }
  49. });
  50. });
  51. //Node construction
  52. exports.createDocument = function () {
  53. return new Node({
  54. type: 'root',
  55. name: 'root',
  56. parent: null,
  57. prev: null,
  58. next: null,
  59. children: [],
  60. 'x-mode': DOCUMENT_MODE.NO_QUIRKS
  61. });
  62. };
  63. exports.createDocumentFragment = function () {
  64. return new Node({
  65. type: 'root',
  66. name: 'root',
  67. parent: null,
  68. prev: null,
  69. next: null,
  70. children: []
  71. });
  72. };
  73. exports.createElement = function (tagName, namespaceURI, attrs) {
  74. var attribs = Object.create(null),
  75. attribsNamespace = Object.create(null),
  76. attribsPrefix = Object.create(null);
  77. for (var i = 0; i < attrs.length; i++) {
  78. var attrName = attrs[i].name;
  79. attribs[attrName] = attrs[i].value;
  80. attribsNamespace[attrName] = attrs[i].namespace;
  81. attribsPrefix[attrName] = attrs[i].prefix;
  82. }
  83. return new Node({
  84. type: tagName === 'script' || tagName === 'style' ? tagName : 'tag',
  85. name: tagName,
  86. namespace: namespaceURI,
  87. attribs: attribs,
  88. 'x-attribsNamespace': attribsNamespace,
  89. 'x-attribsPrefix': attribsPrefix,
  90. children: [],
  91. parent: null,
  92. prev: null,
  93. next: null
  94. });
  95. };
  96. exports.createCommentNode = function (data) {
  97. return new Node({
  98. type: 'comment',
  99. data: data,
  100. parent: null,
  101. prev: null,
  102. next: null
  103. });
  104. };
  105. var createTextNode = function (value) {
  106. return new Node({
  107. type: 'text',
  108. data: value,
  109. parent: null,
  110. prev: null,
  111. next: null
  112. });
  113. };
  114. //Tree mutation
  115. var appendChild = exports.appendChild = function (parentNode, newNode) {
  116. var prev = parentNode.children[parentNode.children.length - 1];
  117. if (prev) {
  118. prev.next = newNode;
  119. newNode.prev = prev;
  120. }
  121. parentNode.children.push(newNode);
  122. newNode.parent = parentNode;
  123. };
  124. var insertBefore = exports.insertBefore = function (parentNode, newNode, referenceNode) {
  125. var insertionIdx = parentNode.children.indexOf(referenceNode),
  126. prev = referenceNode.prev;
  127. if (prev) {
  128. prev.next = newNode;
  129. newNode.prev = prev;
  130. }
  131. referenceNode.prev = newNode;
  132. newNode.next = referenceNode;
  133. parentNode.children.splice(insertionIdx, 0, newNode);
  134. newNode.parent = parentNode;
  135. };
  136. exports.setTemplateContent = function (templateElement, contentElement) {
  137. appendChild(templateElement, contentElement);
  138. };
  139. exports.getTemplateContent = function (templateElement) {
  140. return templateElement.children[0];
  141. };
  142. exports.setDocumentType = function (document, name, publicId, systemId) {
  143. var data = doctype.serializeContent(name, publicId, systemId),
  144. doctypeNode = null;
  145. for (var i = 0; i < document.children.length; i++) {
  146. if (document.children[i].type === 'directive' && document.children[i].name === '!doctype') {
  147. doctypeNode = document.children[i];
  148. break;
  149. }
  150. }
  151. if (doctypeNode) {
  152. doctypeNode.data = data;
  153. doctypeNode['x-name'] = name;
  154. doctypeNode['x-publicId'] = publicId;
  155. doctypeNode['x-systemId'] = systemId;
  156. }
  157. else {
  158. appendChild(document, new Node({
  159. type: 'directive',
  160. name: '!doctype',
  161. data: data,
  162. 'x-name': name,
  163. 'x-publicId': publicId,
  164. 'x-systemId': systemId
  165. }));
  166. }
  167. };
  168. exports.setDocumentMode = function (document, mode) {
  169. document['x-mode'] = mode;
  170. };
  171. exports.getDocumentMode = function (document) {
  172. return document['x-mode'];
  173. };
  174. exports.detachNode = function (node) {
  175. if (node.parent) {
  176. var idx = node.parent.children.indexOf(node),
  177. prev = node.prev,
  178. next = node.next;
  179. node.prev = null;
  180. node.next = null;
  181. if (prev)
  182. prev.next = next;
  183. if (next)
  184. next.prev = prev;
  185. node.parent.children.splice(idx, 1);
  186. node.parent = null;
  187. }
  188. };
  189. exports.insertText = function (parentNode, text) {
  190. var lastChild = parentNode.children[parentNode.children.length - 1];
  191. if (lastChild && lastChild.type === 'text')
  192. lastChild.data += text;
  193. else
  194. appendChild(parentNode, createTextNode(text));
  195. };
  196. exports.insertTextBefore = function (parentNode, text, referenceNode) {
  197. var prevNode = parentNode.children[parentNode.children.indexOf(referenceNode) - 1];
  198. if (prevNode && prevNode.type === 'text')
  199. prevNode.data += text;
  200. else
  201. insertBefore(parentNode, createTextNode(text), referenceNode);
  202. };
  203. exports.adoptAttributes = function (recipient, attrs) {
  204. for (var i = 0; i < attrs.length; i++) {
  205. var attrName = attrs[i].name;
  206. if (typeof recipient.attribs[attrName] === 'undefined') {
  207. recipient.attribs[attrName] = attrs[i].value;
  208. recipient['x-attribsNamespace'][attrName] = attrs[i].namespace;
  209. recipient['x-attribsPrefix'][attrName] = attrs[i].prefix;
  210. }
  211. }
  212. };
  213. //Tree traversing
  214. exports.getFirstChild = function (node) {
  215. return node.children[0];
  216. };
  217. exports.getChildNodes = function (node) {
  218. return node.children;
  219. };
  220. exports.getParentNode = function (node) {
  221. return node.parent;
  222. };
  223. exports.getAttrList = function (element) {
  224. var attrList = [];
  225. for (var name in element.attribs) {
  226. attrList.push({
  227. name: name,
  228. value: element.attribs[name],
  229. namespace: element['x-attribsNamespace'][name],
  230. prefix: element['x-attribsPrefix'][name]
  231. });
  232. }
  233. return attrList;
  234. };
  235. //Node data
  236. exports.getTagName = function (element) {
  237. return element.name;
  238. };
  239. exports.getNamespaceURI = function (element) {
  240. return element.namespace;
  241. };
  242. exports.getTextNodeContent = function (textNode) {
  243. return textNode.data;
  244. };
  245. exports.getCommentNodeContent = function (commentNode) {
  246. return commentNode.data;
  247. };
  248. exports.getDocumentTypeNodeName = function (doctypeNode) {
  249. return doctypeNode['x-name'];
  250. };
  251. exports.getDocumentTypeNodePublicId = function (doctypeNode) {
  252. return doctypeNode['x-publicId'];
  253. };
  254. exports.getDocumentTypeNodeSystemId = function (doctypeNode) {
  255. return doctypeNode['x-systemId'];
  256. };
  257. //Node types
  258. exports.isTextNode = function (node) {
  259. return node.type === 'text';
  260. };
  261. exports.isCommentNode = function (node) {
  262. return node.type === 'comment';
  263. };
  264. exports.isDocumentTypeNode = function (node) {
  265. return node.type === 'directive' && node.name === '!doctype';
  266. };
  267. exports.isElementNode = function (node) {
  268. return !!node.attribs;
  269. };