node.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. CKEDITOR.define( [ 'document/attribute', 'utils', 'ckeditorerror' ], ( Attribute, utils, CKEditorError ) => {
  7. /**
  8. * Abstract document tree node class.
  9. *
  10. * @abstract
  11. * @class document.Node
  12. */
  13. class Node {
  14. /**
  15. * Creates a tree node.
  16. *
  17. * This is an abstract class, so this constructor should not be used directly.
  18. *
  19. * @param {Iterable} attrs Iterable collection of {@link document.Attribute attributes}.
  20. * @constructor
  21. */
  22. constructor( attrs ) {
  23. /**
  24. * Parent element. Null by default. Set by {@link document.Element#insertChildren}.
  25. *
  26. * @readonly
  27. * @property {document.Element|null} parent
  28. */
  29. this.parent = null;
  30. /**
  31. * Attributes set.
  32. *
  33. * Attributes of nodes attached to the document can be changed only be the {@link document.operation.ChangeOperation}.
  34. *
  35. * @private
  36. * @property {Set} _attrs
  37. */
  38. this._attrs = new Set( attrs );
  39. }
  40. /**
  41. * Depth of the node, which equals to total number of its parents.
  42. *
  43. * @readonly
  44. * @property {Number} depth
  45. */
  46. get depth() {
  47. let depth = 0;
  48. let parent = this.parent;
  49. while ( parent ) {
  50. depth++;
  51. parent = parent.parent;
  52. }
  53. return depth;
  54. }
  55. /**
  56. * Nodes next sibling or `null` if it is the last child.
  57. *
  58. * @readonly
  59. * @property {document.Node|null} nextSibling
  60. */
  61. get nextSibling() {
  62. const index = this.getIndex();
  63. return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
  64. }
  65. /**
  66. * Nodes previous sibling or null if it is the last child.
  67. *
  68. * @readonly
  69. * @property {document.Node|null} previousSibling
  70. */
  71. get previousSibling() {
  72. const index = this.getIndex();
  73. return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
  74. }
  75. /**
  76. * The top parent for the node. If node has no parent it is the root itself.
  77. *
  78. * @readonly
  79. * @property {Number} depth
  80. */
  81. get root() {
  82. let root = this; // jscs:ignore safeContextKeyword
  83. while ( root.parent ) {
  84. root = root.parent;
  85. }
  86. return root;
  87. }
  88. /**
  89. * Finds an attribute by a key.
  90. *
  91. * @param {String} attr The attribute key.
  92. * @returns {document.Attribute} The found attribute.
  93. */
  94. getAttr( key ) {
  95. for ( let attr of this._attrs ) {
  96. if ( attr.key == key ) {
  97. return attr.value;
  98. }
  99. }
  100. return null;
  101. }
  102. /**
  103. * Returns attribute iterator. It can be use to create a new element with the same attributes:
  104. *
  105. * const copy = new Element( element.name, element.getAttrs() );
  106. *
  107. * @returns {Iterable.<document.Attribute>} Attribute iterator.
  108. */
  109. getAttrs() {
  110. return this._attrs[ Symbol.iterator ]();
  111. }
  112. /**
  113. * Index of the node in the parent element or null if the node has no parent.
  114. *
  115. * Throws error if the parent element does not contain this node.
  116. *
  117. * @returns {Number|Null} Index of the node in the parent element or null if the node has not parent.
  118. */
  119. getIndex() {
  120. let pos;
  121. if ( !this.parent ) {
  122. return null;
  123. }
  124. // No parent or child doesn't exist in parent's children.
  125. if ( ( pos = this.parent.getChildIndex( this ) ) == -1 ) {
  126. /**
  127. * The node's parent does not contain this node. It means that the document tree is corrupted.
  128. *
  129. * @error node-not-found-in-parent
  130. */
  131. throw new CKEditorError( 'node-not-found-in-parent: The node\'s parent does not contain this node.' );
  132. }
  133. return pos;
  134. }
  135. /**
  136. * Gets path to the node. For example if the node is the second child of the first child of the root then the path
  137. * will be `[ 1, 2 ]`. This path can be used as a parameter of {@link document.Position}.
  138. *
  139. * @returns {Number[]} The path.
  140. */
  141. getPath() {
  142. const path = [];
  143. let node = this; // jscs:ignore safeContextKeyword
  144. while ( node.parent ) {
  145. path.unshift( node.getIndex() );
  146. node = node.parent;
  147. }
  148. return path;
  149. }
  150. /**
  151. * Returns `true` if the node contains an attribute with the same key and value as given or the same key if the
  152. * given parameter is a string.
  153. *
  154. * @param {document.Attribute|String} key An attribute or a key to compare.
  155. * @returns {Boolean} True if node contains given attribute or an attribute with the given key.
  156. */
  157. hasAttr( key ) {
  158. let attr;
  159. // Attribute.
  160. if ( key instanceof Attribute ) {
  161. for ( attr of this._attrs ) {
  162. if ( attr.isEqual( key ) ) {
  163. return true;
  164. }
  165. }
  166. }
  167. // Key.
  168. else {
  169. for ( attr of this._attrs ) {
  170. if ( attr.key == key ) {
  171. return true;
  172. }
  173. }
  174. }
  175. return false;
  176. }
  177. /**
  178. * Removes attribute from the list of attributes.
  179. *
  180. * @param {String} key The attribute key.
  181. */
  182. removeAttr( key ) {
  183. for ( let attr of this._attrs ) {
  184. if ( attr.key == key ) {
  185. this._attrs.delete( attr );
  186. return;
  187. }
  188. }
  189. }
  190. /**
  191. * Sets a given attribute. If the attribute with the same key already exists it will be removed.
  192. *
  193. * @param {document.Attribute} attr Attribute to set.
  194. */
  195. setAttr( attr ) {
  196. this.removeAttr( attr.key );
  197. this._attrs.add( attr );
  198. }
  199. /**
  200. * Custom toJSON method to solve child-parent circular dependencies.
  201. *
  202. * @returns {Object} Clone of this object with the parent property replaced with its name.
  203. */
  204. toJSON() {
  205. const json = utils.clone( this );
  206. // Due to circular references we need to remove parent reference.
  207. json.parent = this.parent ? this.parent.name : null;
  208. return json;
  209. }
  210. }
  211. return Node;
  212. } );