node.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import { ElementType, isTag as isTagRaw } from "domelementtype";
  2. /**
  3. * This object will be used as the prototype for Nodes when creating a
  4. * DOM-Level-1-compliant structure.
  5. */
  6. export class Node {
  7. constructor() {
  8. /** Parent of the node */
  9. this.parent = null;
  10. /** Previous sibling */
  11. this.prev = null;
  12. /** Next sibling */
  13. this.next = null;
  14. /** The start index of the node. Requires `withStartIndices` on the handler to be `true. */
  15. this.startIndex = null;
  16. /** The end index of the node. Requires `withEndIndices` on the handler to be `true. */
  17. this.endIndex = null;
  18. }
  19. // Read-write aliases for properties
  20. /**
  21. * Same as {@link parent}.
  22. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
  23. */
  24. get parentNode() {
  25. return this.parent;
  26. }
  27. set parentNode(parent) {
  28. this.parent = parent;
  29. }
  30. /**
  31. * Same as {@link prev}.
  32. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
  33. */
  34. get previousSibling() {
  35. return this.prev;
  36. }
  37. set previousSibling(prev) {
  38. this.prev = prev;
  39. }
  40. /**
  41. * Same as {@link next}.
  42. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
  43. */
  44. get nextSibling() {
  45. return this.next;
  46. }
  47. set nextSibling(next) {
  48. this.next = next;
  49. }
  50. /**
  51. * Clone this node, and optionally its children.
  52. *
  53. * @param recursive Clone child nodes as well.
  54. * @returns A clone of the node.
  55. */
  56. cloneNode(recursive = false) {
  57. return cloneNode(this, recursive);
  58. }
  59. }
  60. /**
  61. * A node that contains some data.
  62. */
  63. export class DataNode extends Node {
  64. /**
  65. * @param data The content of the data node
  66. */
  67. constructor(data) {
  68. super();
  69. this.data = data;
  70. }
  71. /**
  72. * Same as {@link data}.
  73. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
  74. */
  75. get nodeValue() {
  76. return this.data;
  77. }
  78. set nodeValue(data) {
  79. this.data = data;
  80. }
  81. }
  82. /**
  83. * Text within the document.
  84. */
  85. export class Text extends DataNode {
  86. constructor() {
  87. super(...arguments);
  88. this.type = ElementType.Text;
  89. }
  90. get nodeType() {
  91. return 3;
  92. }
  93. }
  94. /**
  95. * Comments within the document.
  96. */
  97. export class Comment extends DataNode {
  98. constructor() {
  99. super(...arguments);
  100. this.type = ElementType.Comment;
  101. }
  102. get nodeType() {
  103. return 8;
  104. }
  105. }
  106. /**
  107. * Processing instructions, including doc types.
  108. */
  109. export class ProcessingInstruction extends DataNode {
  110. constructor(name, data) {
  111. super(data);
  112. this.name = name;
  113. this.type = ElementType.Directive;
  114. }
  115. get nodeType() {
  116. return 1;
  117. }
  118. }
  119. /**
  120. * A `Node` that can have children.
  121. */
  122. export class NodeWithChildren extends Node {
  123. /**
  124. * @param children Children of the node. Only certain node types can have children.
  125. */
  126. constructor(children) {
  127. super();
  128. this.children = children;
  129. }
  130. // Aliases
  131. /** First child of the node. */
  132. get firstChild() {
  133. var _a;
  134. return (_a = this.children[0]) !== null && _a !== void 0 ? _a : null;
  135. }
  136. /** Last child of the node. */
  137. get lastChild() {
  138. return this.children.length > 0
  139. ? this.children[this.children.length - 1]
  140. : null;
  141. }
  142. /**
  143. * Same as {@link children}.
  144. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
  145. */
  146. get childNodes() {
  147. return this.children;
  148. }
  149. set childNodes(children) {
  150. this.children = children;
  151. }
  152. }
  153. export class CDATA extends NodeWithChildren {
  154. constructor() {
  155. super(...arguments);
  156. this.type = ElementType.CDATA;
  157. }
  158. get nodeType() {
  159. return 4;
  160. }
  161. }
  162. /**
  163. * The root node of the document.
  164. */
  165. export class Document extends NodeWithChildren {
  166. constructor() {
  167. super(...arguments);
  168. this.type = ElementType.Root;
  169. }
  170. get nodeType() {
  171. return 9;
  172. }
  173. }
  174. /**
  175. * An element within the DOM.
  176. */
  177. export class Element extends NodeWithChildren {
  178. /**
  179. * @param name Name of the tag, eg. `div`, `span`.
  180. * @param attribs Object mapping attribute names to attribute values.
  181. * @param children Children of the node.
  182. */
  183. constructor(name, attribs, children = [], type = name === "script"
  184. ? ElementType.Script
  185. : name === "style"
  186. ? ElementType.Style
  187. : ElementType.Tag) {
  188. super(children);
  189. this.name = name;
  190. this.attribs = attribs;
  191. this.type = type;
  192. }
  193. get nodeType() {
  194. return 1;
  195. }
  196. // DOM Level 1 aliases
  197. /**
  198. * Same as {@link name}.
  199. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
  200. */
  201. get tagName() {
  202. return this.name;
  203. }
  204. set tagName(name) {
  205. this.name = name;
  206. }
  207. get attributes() {
  208. return Object.keys(this.attribs).map((name) => {
  209. var _a, _b;
  210. return ({
  211. name,
  212. value: this.attribs[name],
  213. namespace: (_a = this["x-attribsNamespace"]) === null || _a === void 0 ? void 0 : _a[name],
  214. prefix: (_b = this["x-attribsPrefix"]) === null || _b === void 0 ? void 0 : _b[name],
  215. });
  216. });
  217. }
  218. }
  219. /**
  220. * @param node Node to check.
  221. * @returns `true` if the node is a `Element`, `false` otherwise.
  222. */
  223. export function isTag(node) {
  224. return isTagRaw(node);
  225. }
  226. /**
  227. * @param node Node to check.
  228. * @returns `true` if the node has the type `CDATA`, `false` otherwise.
  229. */
  230. export function isCDATA(node) {
  231. return node.type === ElementType.CDATA;
  232. }
  233. /**
  234. * @param node Node to check.
  235. * @returns `true` if the node has the type `Text`, `false` otherwise.
  236. */
  237. export function isText(node) {
  238. return node.type === ElementType.Text;
  239. }
  240. /**
  241. * @param node Node to check.
  242. * @returns `true` if the node has the type `Comment`, `false` otherwise.
  243. */
  244. export function isComment(node) {
  245. return node.type === ElementType.Comment;
  246. }
  247. /**
  248. * @param node Node to check.
  249. * @returns `true` if the node has the type `ProcessingInstruction`, `false` otherwise.
  250. */
  251. export function isDirective(node) {
  252. return node.type === ElementType.Directive;
  253. }
  254. /**
  255. * @param node Node to check.
  256. * @returns `true` if the node has the type `ProcessingInstruction`, `false` otherwise.
  257. */
  258. export function isDocument(node) {
  259. return node.type === ElementType.Root;
  260. }
  261. /**
  262. * @param node Node to check.
  263. * @returns `true` if the node has children, `false` otherwise.
  264. */
  265. export function hasChildren(node) {
  266. return Object.prototype.hasOwnProperty.call(node, "children");
  267. }
  268. /**
  269. * Clone a node, and optionally its children.
  270. *
  271. * @param recursive Clone child nodes as well.
  272. * @returns A clone of the node.
  273. */
  274. export function cloneNode(node, recursive = false) {
  275. let result;
  276. if (isText(node)) {
  277. result = new Text(node.data);
  278. }
  279. else if (isComment(node)) {
  280. result = new Comment(node.data);
  281. }
  282. else if (isTag(node)) {
  283. const children = recursive ? cloneChildren(node.children) : [];
  284. const clone = new Element(node.name, { ...node.attribs }, children);
  285. children.forEach((child) => (child.parent = clone));
  286. if (node.namespace != null) {
  287. clone.namespace = node.namespace;
  288. }
  289. if (node["x-attribsNamespace"]) {
  290. clone["x-attribsNamespace"] = { ...node["x-attribsNamespace"] };
  291. }
  292. if (node["x-attribsPrefix"]) {
  293. clone["x-attribsPrefix"] = { ...node["x-attribsPrefix"] };
  294. }
  295. result = clone;
  296. }
  297. else if (isCDATA(node)) {
  298. const children = recursive ? cloneChildren(node.children) : [];
  299. const clone = new CDATA(children);
  300. children.forEach((child) => (child.parent = clone));
  301. result = clone;
  302. }
  303. else if (isDocument(node)) {
  304. const children = recursive ? cloneChildren(node.children) : [];
  305. const clone = new Document(children);
  306. children.forEach((child) => (child.parent = clone));
  307. if (node["x-mode"]) {
  308. clone["x-mode"] = node["x-mode"];
  309. }
  310. result = clone;
  311. }
  312. else if (isDirective(node)) {
  313. const instruction = new ProcessingInstruction(node.name, node.data);
  314. if (node["x-name"] != null) {
  315. instruction["x-name"] = node["x-name"];
  316. instruction["x-publicId"] = node["x-publicId"];
  317. instruction["x-systemId"] = node["x-systemId"];
  318. }
  319. result = instruction;
  320. }
  321. else {
  322. throw new Error(`Not implemented yet: ${node.type}`);
  323. }
  324. result.startIndex = node.startIndex;
  325. result.endIndex = node.endIndex;
  326. if (node.sourceCodeLocation != null) {
  327. result.sourceCodeLocation = node.sourceCodeLocation;
  328. }
  329. return result;
  330. }
  331. function cloneChildren(childs) {
  332. const children = childs.map((child) => cloneNode(child, true));
  333. for (let i = 1; i < children.length; i++) {
  334. children[i].prev = children[i - 1];
  335. children[i - 1].next = children[i];
  336. }
  337. return children;
  338. }