8
0

textproxy.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * `TextProxy` represents a part of {@link engine.model.Text text node}.
  7. *
  8. * Since {@link engine.model.Position positions} can be placed between characters of a text node,
  9. * {@link engine.model.Range ranges} may contain only parts of text nodes. When {@link engine.model.Range#getItems getting items}
  10. * contained in such range, we need to represent a part of that text node, since returning the whole text node would be incorrect.
  11. * `TextProxy` solves this issue.
  12. *
  13. * `TextProxy` has an API similar to {@link engine.model.Text Text} and allows to do most of the common tasks performed
  14. * on model nodes.
  15. *
  16. * **Note:** Some `TextProxy` instances may represent whole text node, not just a part of it.
  17. *
  18. * **Note:** `TextProxy` is not an instance of {@link engine.model.Node node}. Keep this in mind when using it as a
  19. * parameter of methods.
  20. *
  21. * **Note:** `TextProxy` is readonly interface. If you want to perform changes on model data represented by a `TextProxy`
  22. * use {@link engine.model.writer model writer API}.
  23. *
  24. * **Note:** `TextProxy` instances are created on the fly, basing on the current state of model. Because of this, it is
  25. * highly unrecommended to store references to `TextProxy` instances. `TextProxy` instances are not refreshed when
  26. * model changes, so they might get invalidated. Instead, consider creating {@link engine.model.LivePosition live position}.
  27. *
  28. * `TextProxy` instances are created by {@link engine.model.TreeWalker model tree walker}. You should not need to create
  29. * an instance of this class by your own.
  30. *
  31. * @memberOf engine.model
  32. */
  33. export default class TextProxy {
  34. /**
  35. * Creates a text proxy.
  36. *
  37. * @protected
  38. * @param {engine.model.Text} textNode Text node which part is represented by this text proxy.
  39. * @param {Number} offsetInText Offset in {@link engine.model.TextProxy#textNode text node} from which the text proxy starts.
  40. * @param {Number} length Text proxy length, that is how many text node's characters, starting from `offsetInText` it represents.
  41. * @constructor
  42. */
  43. constructor( textNode, offsetInText, length ) {
  44. /**
  45. * Text node which part is represented by this text proxy.
  46. *
  47. * @readonly
  48. * @member {engine.model.Text} engine.model.TextProxy#textNode
  49. */
  50. this.textNode = textNode;
  51. /**
  52. * Text data represented by this text proxy.
  53. *
  54. * @readonly
  55. * @member {String} engine.model.TextProxy#data
  56. */
  57. this.data = textNode.data.substring( offsetInText, offsetInText + length );
  58. /**
  59. * Offset in {@link engine.model.TextProxy#textNode text node} from which the text proxy starts.
  60. *
  61. * @readonly
  62. * @member {Number} engine.model.TextProxy#offsetInText
  63. */
  64. this.offsetInText = offsetInText;
  65. }
  66. /**
  67. * Offset at which this text proxy starts in it's parent.
  68. *
  69. * @see engine.model.Node#startOffset
  70. * @readonly
  71. * @type {Number}
  72. */
  73. get startOffset() {
  74. return this.textNode.startOffset !== null ? this.textNode.startOffset + this.offsetInText : null;
  75. }
  76. /**
  77. * Offset size of this text proxy. Equal to the number of characters represented by the text proxy.
  78. *
  79. * @see engine.model.Node#offsetSize
  80. * @readonly
  81. * @type {Number}
  82. */
  83. get offsetSize() {
  84. return this.data.length;
  85. }
  86. /**
  87. * Offset at which this text proxy ends in it's parent.
  88. *
  89. * @see engine.model.Node#endOffset
  90. * @readonly
  91. * @type {Number}
  92. */
  93. get endOffset() {
  94. return this.startOffset !== null ? this.startOffset + this.offsetSize : null;
  95. }
  96. /**
  97. * Flag indicating whether `TextProxy` instance covers only part of the original {@link engine.model.Text text node}
  98. * (`true`) or the whole text node (`false`).
  99. *
  100. * This is `false` when text proxy starts at the very beginning of {@link engine.model.TextProxy#textNode textNode}
  101. * ({@link engine.model.TextProxy#offsetInText offsetInText} equals `0`) and text proxy sizes is equal to
  102. * text node size.
  103. *
  104. * @readonly
  105. * @type {Boolean}
  106. */
  107. get isPartial() {
  108. return this.offsetInText !== 0 || this.offsetSize !== this.textNode.offsetSize;
  109. }
  110. /**
  111. * Parent of this text proxy, which is same as parent of text node represented by this text proxy.
  112. *
  113. * @readonly
  114. * @type {engine.model.Element|engine.model.DocumentFragment|null}
  115. */
  116. get parent() {
  117. return this.textNode.parent;
  118. }
  119. /**
  120. * Root of this text proxy, which is same as root of text node represented by this text proxy.
  121. *
  122. * @readonly
  123. * @type {engine.model.Node|engine.model.DocumentFragment}
  124. */
  125. get root() {
  126. return this.textNode.root;
  127. }
  128. /**
  129. * {@link engine.model.Document Document} that owns text node represented by this text proxy or `null` if the text node
  130. * has no parent or is inside a {@link engine.model.DocumentFragment DocumentFragment}.
  131. *
  132. * @readonly
  133. * @type {engine.model.Document|null}
  134. */
  135. get document() {
  136. return this.textNode.document;
  137. }
  138. /**
  139. * Gets path to this text proxy.
  140. *
  141. * @see engine.model.Node#getPath
  142. * @readonly
  143. * @type {Array.<Number>}
  144. */
  145. getPath() {
  146. const path = this.textNode.getPath();
  147. if ( path.length > 0 ) {
  148. path[ path.length - 1 ] += this.offsetInText;
  149. }
  150. return path;
  151. }
  152. /**
  153. * Returns ancestors array of this text proxy.
  154. *
  155. * @param {Object} options Options object.
  156. * @param {Boolean} [options.includeNode=false] When set to `true` this text proxy will be also included in parent's array.
  157. * @param {Boolean} [options.parentFirst=false] When set to `true`, array will be sorted from text proxy parent to root element,
  158. * otherwise root element will be the first item in the array.
  159. * @returns {Array} Array with ancestors.
  160. */
  161. getAncestors( options = { includeNode: false, parentFirst: false } ) {
  162. const ancestors = [];
  163. let parent = options.includeNode ? this : this.parent;
  164. while ( parent ) {
  165. ancestors[ options.parentFirst ? 'push' : 'unshift' ]( parent );
  166. parent = parent.parent;
  167. }
  168. return ancestors;
  169. }
  170. /**
  171. * Checks if this text proxy has an attribute for given key.
  172. *
  173. * @param {String} key Key of attribute to check.
  174. * @returns {Boolean} `true` if attribute with given key is set on text proxy, `false` otherwise.
  175. */
  176. hasAttribute( key ) {
  177. return this.textNode.hasAttribute( key );
  178. }
  179. /**
  180. * Gets an attribute value for given key or `undefined` if that attribute is not set on text proxy.
  181. *
  182. * @param {String} key Key of attribute to look for.
  183. * @returns {*} Attribute value or `undefined`.
  184. */
  185. getAttribute( key ) {
  186. return this.textNode.getAttribute( key );
  187. }
  188. /**
  189. * Returns iterator that iterates over this node's attributes. Attributes are returned as arrays containing two
  190. * items. First one is attribute key and second is attribute value.
  191. *
  192. * This format is accepted by native `Map` object and also can be passed in `Node` constructor.
  193. *
  194. * @returns {Iterable.<*>}
  195. */
  196. getAttributes() {
  197. return this.textNode.getAttributes();
  198. }
  199. /**
  200. * Returns iterator that iterates over this node's attribute keys.
  201. *
  202. * @returns {Iterator.<String>}
  203. */
  204. getAttributeKeys() {
  205. return this.textNode.getAttributeKeys();
  206. }
  207. }