node.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. * Index of the node in the parent element or null if the node has no parent.
  42. *
  43. * Throws error if the parent element does not contain this node.
  44. *
  45. * @returns {Number|Null} Index of the node in the parent element or null if the node has not parent.
  46. */
  47. getIndex() {
  48. let pos;
  49. if ( !this.parent ) {
  50. return null;
  51. }
  52. // No parent or child doesn't exist in parent's children.
  53. if ( ( pos = this.parent.getChildIndex( this ) ) == -1 ) {
  54. /**
  55. * The node's parent does not contain this node. It means that the document tree is corrupted.
  56. *
  57. * @error node-not-found-in-parent
  58. */
  59. throw new CKEditorError( 'node-not-found-in-parent: The node\'s parent does not contain this node.' );
  60. }
  61. return pos;
  62. }
  63. /**
  64. * Depth of the node, which equals to total number of its parents.
  65. *
  66. * @readonly
  67. * @property {Number} depth
  68. */
  69. get depth() {
  70. let depth = 0;
  71. let parent = this.parent;
  72. while ( parent ) {
  73. depth++;
  74. parent = parent.parent;
  75. }
  76. return depth;
  77. }
  78. /**
  79. * The top parent for the node. If node has no parent it is the root itself.
  80. *
  81. * @readonly
  82. * @property {Number} depth
  83. */
  84. get root() {
  85. let root = this; // jscs:ignore safeContextKeyword
  86. while ( root.parent ) {
  87. root = root.parent;
  88. }
  89. return root;
  90. }
  91. /**
  92. * Nodes next sibling or `null` if it is the last child.
  93. *
  94. * @readonly
  95. * @property {document.Node|null} nextSibling
  96. */
  97. get nextSibling() {
  98. const index = this.getIndex();
  99. return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
  100. }
  101. /**
  102. * Nodes previous sibling or null if it is the last child.
  103. *
  104. * @readonly
  105. * @property {document.Node|null} previousSibling
  106. */
  107. get previousSibling() {
  108. const index = this.getIndex();
  109. return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
  110. }
  111. /**
  112. * Returns `true` if the node contains an attribute with the same key and value as given or the same key if the
  113. * given parameter is a string.
  114. *
  115. * @param {document.Attribute|String} attr An attribute or a key to compare.
  116. * @returns {Boolean} True if node contains given attribute or an attribute with the given key.
  117. */
  118. hasAttr( key ) {
  119. let attr;
  120. // Attribute.
  121. if ( key instanceof Attribute ) {
  122. for ( attr of this._attrs ) {
  123. if ( attr.isEqual( key ) ) {
  124. return true;
  125. }
  126. }
  127. }
  128. // Key.
  129. else {
  130. for ( attr of this._attrs ) {
  131. if ( attr.key == key ) {
  132. return true;
  133. }
  134. }
  135. }
  136. return false;
  137. }
  138. /**
  139. * Finds an attribute by a key.
  140. *
  141. * @param {String} attr The attribute key.
  142. * @returns {document.Attribute} The found attribute.
  143. */
  144. getAttr( key ) {
  145. for ( let attr of this._attrs ) {
  146. if ( attr.key == key ) {
  147. return attr.value;
  148. }
  149. }
  150. return null;
  151. }
  152. /**
  153. * Removes attribute from the list of attributes.
  154. *
  155. * @param {String} key The attribute key.
  156. */
  157. removeAttr( key ) {
  158. for ( let attr of this._attrs ) {
  159. if ( attr.key == key ) {
  160. this._attrs.delete( attr );
  161. return;
  162. }
  163. }
  164. }
  165. /**
  166. * Sets a given attribute. If the attribute with the same key already exists it will be removed.
  167. *
  168. * @param {document.Attribute} attr Attribute to set.
  169. */
  170. setAttr( attr ) {
  171. this.removeAttr( attr.key );
  172. this._attrs.add( attr );
  173. }
  174. /**
  175. * Gets path to the node. For example if the node is the second child of the first child of the root then the path
  176. * will be `[ 1, 2 ]`. This path can be used as a parameter of {@link document.Position}.
  177. *
  178. * @returns {Number[]} The path.
  179. */
  180. getPath() {
  181. const path = [];
  182. let node = this; // jscs:ignore safeContextKeyword
  183. while ( node.parent ) {
  184. path.unshift( node.getIndex() );
  185. node = node.parent;
  186. }
  187. return path;
  188. }
  189. /**
  190. * Custom toJSON method to solve child-parent circular dependencies.
  191. *
  192. * @returns {Object} Clone of this object with the parent property replaced with its name.
  193. */
  194. toJSON() {
  195. const json = utils.clone( this );
  196. // Due to circular references we need to remove parent reference.
  197. json.parent = this.parent ? this.parent.name : null;
  198. return json;
  199. }
  200. /**
  201. * Gets the number of attributes.
  202. *
  203. * @protected
  204. * @returns {Number} Number of attributes.
  205. */
  206. _getAttrCount() {
  207. let count = 0;
  208. for ( let attr of this._attrs ) { // jshint ignore:line
  209. count++;
  210. }
  211. return count;
  212. }
  213. }
  214. return Node;
  215. } );