index.js 8.3 KB

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