Node.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2017-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Generic Node classes for node trees
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {NodeFactory} from './NodeFactory.js';
  23. /**
  24. * PropertyList and Property are for string data like
  25. * attributes and other properties
  26. */
  27. export type Property = string | number | boolean;
  28. export type PropertyList = {[key: string]: Property};
  29. /*********************************************************/
  30. /**
  31. * The generic Node interface
  32. */
  33. export interface Node {
  34. readonly kind: string;
  35. /**
  36. * The NodeFactory to use to create additional nodes, as needed
  37. */
  38. readonly factory: NodeFactory<Node, NodeClass>;
  39. parent: Node;
  40. childNodes: Node[];
  41. /**
  42. * @param {string} name The name of the property to set
  43. * @param {Property} value The value to which the property will be set
  44. */
  45. setProperty(name: string, value: Property): void;
  46. /**
  47. * @param {string} name The name of the property to get
  48. * @return {Property} The value of the named property
  49. */
  50. getProperty(name: string): Property;
  51. /**
  52. * @return {string[]} An array of the names of every property currently defined
  53. */
  54. getPropertyNames(): string[];
  55. /**
  56. * @return {PropertyList} The propery list containing all the properties of the node
  57. */
  58. getAllProperties(): PropertyList;
  59. /**
  60. * @param {string[]} names The names of the properties to be removed
  61. */
  62. removeProperty(...names: string[]): void;
  63. /**
  64. * @param {string} kind The type of node to test for
  65. * @return {boolean} True when the node is of the given type
  66. */
  67. isKind(kind: string): boolean;
  68. /**
  69. * @param {Node[]} children The child nodes to add to this node
  70. */
  71. setChildren(children: Node[]): void;
  72. /**
  73. * @param {Node} child A node to add to this node's children
  74. * @return {Node} The child node that was added
  75. */
  76. appendChild(child: Node): Node;
  77. /**
  78. * @param {Node} newChild A child node to be inserted
  79. * @param {Node} oldChild A child node to be replaced
  80. * @return {Node} The old child node that was removed
  81. */
  82. replaceChild(newChild: Node, oldChild: Node): Node;
  83. /**
  84. * @param {Node} child Child node to be removed
  85. * @return {Node} The old child node that was removed
  86. */
  87. removeChild(child: Node): Node;
  88. /**
  89. * @param {Node} child A child node whose index in childNodes is desired
  90. * @return {number} The index of the child in childNodes, or null if not found
  91. */
  92. childIndex(child: Node): number;
  93. /**
  94. * Make a deep copy of the node (but with no parent).
  95. */
  96. copy(): Node;
  97. /**
  98. * @param {string} kind The kind of nodes to be located in the tree
  99. * @return {Node[]} An array of nodes that are children (at any depth) of the given kind
  100. */
  101. findNodes(kind: string): Node[];
  102. /**
  103. * @param {Function} func A function to apply to each node in the tree rooted at this node
  104. * @param {any} data Data to pass to the function (as state information)
  105. */
  106. walkTree(func: (node: Node, data?: any) => void, data?: any): void;
  107. }
  108. /*********************************************************/
  109. /**
  110. * The generic Node class interface
  111. */
  112. export interface NodeClass {
  113. /**
  114. * @param {NodeFactory} factory The NodeFactory to use to create new nodes when needed
  115. * @param {PropertyList} properties Any properties to be added to the node, if any
  116. * @param {Node[]} children The initial child nodes, if any
  117. * @return {Node} The newly created node
  118. */
  119. new (factory: NodeFactory<Node, NodeClass>, properties?: PropertyList, children?: Node[]): Node;
  120. }
  121. /*********************************************************/
  122. /**
  123. * The abstract Node class
  124. */
  125. export abstract class AbstractNode implements Node {
  126. /**
  127. * The parent node for this one
  128. */
  129. public parent: Node = null;
  130. /**
  131. * The properties for this node
  132. */
  133. protected properties: PropertyList = {};
  134. /**
  135. * The children for this node
  136. */
  137. public childNodes: Node[] = [];
  138. /**
  139. * @param {NodeFactory} factory The NodeFactory to use to create new nodes when needed
  140. * @param {PropertyList} properties Any properties to be added to the node, if any
  141. * @param {Node[]} children The initial child nodes, if any
  142. *
  143. * @constructor
  144. * @implements {Node}
  145. */
  146. constructor(readonly factory: NodeFactory<Node, NodeClass>, properties: PropertyList = {}, children: Node[] = []) {
  147. for (const name of Object.keys(properties)) {
  148. this.setProperty(name, properties[name]);
  149. }
  150. if (children.length) {
  151. this.setChildren(children);
  152. }
  153. }
  154. /**
  155. * @override
  156. */
  157. public get kind() {
  158. return 'unknown';
  159. }
  160. /**
  161. * @override
  162. */
  163. public setProperty(name: string, value: Property) {
  164. this.properties[name] = value;
  165. }
  166. /**
  167. * @override
  168. */
  169. public getProperty(name: string) {
  170. return this.properties[name];
  171. }
  172. /**
  173. * @override
  174. */
  175. public getPropertyNames() {
  176. return Object.keys(this.properties);
  177. }
  178. /**
  179. * @override
  180. */
  181. public getAllProperties() {
  182. return this.properties;
  183. }
  184. /**
  185. * @override
  186. */
  187. public removeProperty(...names: string[]) {
  188. for (const name of names) {
  189. delete this.properties[name];
  190. }
  191. }
  192. /**
  193. * @override
  194. */
  195. public isKind(kind: string): boolean {
  196. return this.factory.nodeIsKind(this, kind);
  197. }
  198. /**
  199. * @override
  200. */
  201. public setChildren(children: Node[]) {
  202. this.childNodes = [];
  203. for (let child of children) {
  204. this.appendChild(child);
  205. }
  206. }
  207. /**
  208. * @override
  209. */
  210. public appendChild(child: Node) {
  211. this.childNodes.push(child);
  212. child.parent = this;
  213. return child;
  214. }
  215. /**
  216. * @override
  217. */
  218. public replaceChild(newChild: Node, oldChild: Node) {
  219. let i = this.childIndex(oldChild);
  220. // If i === null should we error? return null? silently fail?
  221. if (i !== null) {
  222. this.childNodes[i] = newChild;
  223. newChild.parent = this;
  224. oldChild.parent = null;
  225. }
  226. return newChild;
  227. }
  228. /**
  229. * @override
  230. */
  231. public removeChild(child: Node) {
  232. const i = this.childIndex(child);
  233. if (i !== null) {
  234. this.childNodes.splice(i, 1);
  235. child.parent = null;
  236. }
  237. return child;
  238. }
  239. /**
  240. * @override
  241. */
  242. public childIndex(node: Node) {
  243. let i = this.childNodes.indexOf(node);
  244. return (i === -1 ? null : i);
  245. }
  246. /**
  247. * @override
  248. */
  249. public copy() {
  250. const node = (this as AbstractNode).factory.create(this.kind) as AbstractNode;
  251. node.properties = {...this.properties};
  252. for (const child of this.childNodes || []) {
  253. if (child) {
  254. node.appendChild(child.copy());
  255. }
  256. }
  257. return node;
  258. }
  259. /**
  260. * @override
  261. */
  262. public findNodes(kind: string) {
  263. let nodes: Node[] = [];
  264. this.walkTree((node: Node) => {
  265. if (node.isKind(kind)) {
  266. nodes.push(node);
  267. }
  268. });
  269. return nodes;
  270. }
  271. /**
  272. * @override
  273. */
  274. public walkTree(func: (node: Node, data?: any) => void, data?: any) {
  275. func(this, data);
  276. for (const child of this.childNodes) {
  277. if (child) {
  278. child.walkTree(func, data);
  279. }
  280. }
  281. return data;
  282. }
  283. /**
  284. * Simple string version for debugging, just to get the structure.
  285. */
  286. public toString() {
  287. return this.kind + '(' + this.childNodes.join(',') + ')';
  288. }
  289. }
  290. /*********************************************************/
  291. /**
  292. * The abstract EmptyNode class
  293. */
  294. export abstract class AbstractEmptyNode extends AbstractNode {
  295. /**
  296. * We don't have children, so ignore these methods
  297. */
  298. /**
  299. * @override
  300. */
  301. public setChildren(_children: Node[]) {
  302. }
  303. /**
  304. * @override
  305. */
  306. public appendChild(child: Node) {
  307. return child;
  308. }
  309. /**
  310. * @override
  311. */
  312. public replaceChild(_newChild: Node, oldChild: Node) {
  313. return oldChild;
  314. }
  315. /**
  316. * @override
  317. */
  318. public childIndex(_node: Node) {
  319. return null as number;
  320. }
  321. /**
  322. * Don't step into children (there aren't any)
  323. *
  324. * @override
  325. */
  326. public walkTree(func: (node: Node, data?: any) => void, data?: any) {
  327. func(this, data);
  328. return data;
  329. }
  330. /**
  331. * Simple string version for debugging, just to get the structure.
  332. */
  333. public toString() {
  334. return this.kind;
  335. }
  336. }