range.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module engine/model/range
  7. */
  8. import Position from './position.js';
  9. import TreeWalker from './treewalker.js';
  10. /**
  11. * Range class. Range is iterable.
  12. */
  13. export default class Range {
  14. /**
  15. * Creates a range spanning from `start` position to `end` position.
  16. *
  17. * **Note:** Constructor creates it's own {@link module:engine/model/position~Position Position} instances basing on passed values.
  18. *
  19. * @param {module:engine/model/position~Position} start Start position.
  20. * @param {module:engine/model/position~Position} [end] End position. If not set, range will be collapsed at `start` position.
  21. */
  22. constructor( start, end = null ) {
  23. /**
  24. * Start position.
  25. *
  26. * @readonly
  27. * @member {module:engine/model/position~Position}
  28. */
  29. this.start = Position.createFromPosition( start );
  30. /**
  31. * End position.
  32. *
  33. * @readonly
  34. * @member {module:engine/model/position~Position}
  35. */
  36. this.end = end ? Position.createFromPosition( end ) : Position.createFromPosition( start );
  37. }
  38. /**
  39. * Returns an iterator that iterates over all {@link module:engine/model/item~Item items} that are in this range and returns
  40. * them together with additional information like length or {@link module:engine/model/position~Position positions},
  41. * grouped as {@link module:engine/model/treewalker~TreeWalkerValue}.
  42. * It iterates over all {@link module:engine/model/textproxy~TextProxy text contents} that are inside the range
  43. * and all the {@link module:engine/model/element~Element}s that are entered into when iterating over this range.
  44. *
  45. * This iterator uses {@link module:engine/model/treewalker~TreeWalker} with `boundaries` set to this range
  46. * and `ignoreElementEnd` option set to `true`.
  47. *
  48. * @returns {Iterable.<module:engine/model/treewalker~TreeWalkerValue>}
  49. */
  50. *[ Symbol.iterator ]() {
  51. yield* new TreeWalker( { boundaries: this, ignoreElementEnd: true } );
  52. }
  53. /**
  54. * Returns whether the range is collapsed, that is if {@link #start start} and
  55. * {@link #end end} positions are equal.
  56. *
  57. * @type {Boolean}
  58. */
  59. get isCollapsed() {
  60. return this.start.isEqual( this.end );
  61. }
  62. /**
  63. * Returns whether this range is flat, that is if {@link #start start} position and
  64. * {@link #end end} position are in the same {@link module:engine/model/position~Position#parent parent}.
  65. *
  66. * @type {Boolean}
  67. */
  68. get isFlat() {
  69. return this.start.parent === this.end.parent;
  70. }
  71. /**
  72. * Returns whether this range has no nodes in it, that is if {@link #start start} position and
  73. * {@link #end end} position are {@link module:engine/model/position~Position#isTouching touching}.
  74. *
  75. * **Note:** A range may be empty, but not {@link #isCollapsed collapsed}.
  76. *
  77. * @type {Boolean}
  78. */
  79. get isEmpty() {
  80. return this.start.isTouching( this.end );
  81. }
  82. /**
  83. * Range root element.
  84. *
  85. * @type {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
  86. */
  87. get root() {
  88. return this.start.root;
  89. }
  90. /**
  91. * Checks whether this range contains given {@link module:engine/model/position~Position position}.
  92. *
  93. * @param {module:engine/model/position~Position} position Position to check.
  94. * @returns {Boolean} `true` if given {@link module:engine/model/position~Position position} is contained in this range, `false` otherwise.
  95. */
  96. containsPosition( position ) {
  97. return position.isAfter( this.start ) && position.isBefore( this.end );
  98. }
  99. /**
  100. * Checks whether this range contains given {@link ~Range range}.
  101. *
  102. * @param {module:engine/model/range~Range} otherRange Range to check.
  103. * @returns {Boolean} `true` if given {@link ~Range range} boundaries are contained by this range, `false` otherwise.
  104. */
  105. containsRange( otherRange ) {
  106. return this.containsPosition( otherRange.start ) && this.containsPosition( otherRange.end );
  107. }
  108. /**
  109. * Two ranges are equal if their {@link #start start} and
  110. * {@link #end end} positions are equal.
  111. *
  112. * @param {module:engine/model/range~Range} otherRange Range to compare with.
  113. * @returns {Boolean} `true` if ranges are equal, `false` otherwise.
  114. */
  115. isEqual( otherRange ) {
  116. return this.start.isEqual( otherRange.start ) && this.end.isEqual( otherRange.end );
  117. }
  118. /**
  119. * Checks and returns whether this range intersects with given range.
  120. *
  121. * @param {module:engine/model/range~Range} otherRange Range to compare with.
  122. * @returns {Boolean} `true` if ranges intersect, `false` otherwise.
  123. */
  124. isIntersecting( otherRange ) {
  125. return this.start.isBefore( otherRange.end ) && this.end.isAfter( otherRange.start );
  126. }
  127. /**
  128. * Computes which part(s) of this {@link ~Range range} is not a part of given {@link ~Range range}.
  129. * Returned array contains zero, one or two {@link ~Range ranges}.
  130. *
  131. * Examples:
  132. *
  133. * let range = new Range( new Position( root, [ 2, 7 ] ), new Position( root, [ 4, 0, 1 ] ) );
  134. * let otherRange = new Range( new Position( root, [ 1 ] ), new Position( root, [ 5 ] ) );
  135. * let transformed = range.getDifference( otherRange );
  136. * // transformed array has no ranges because `otherRange` contains `range`
  137. *
  138. * otherRange = new Range( new Position( root, [ 1 ] ), new Position( root, [ 3 ] ) );
  139. * transformed = range.getDifference( otherRange );
  140. * // transformed array has one range: from [ 3 ] to [ 4, 0, 1 ]
  141. *
  142. * otherRange = new Range( new Position( root, [ 3 ] ), new Position( root, [ 4 ] ) );
  143. * transformed = range.getDifference( otherRange );
  144. * // transformed array has two ranges: from [ 2, 7 ] to [ 3 ] and from [ 4 ] to [ 4, 0, 1 ]
  145. *
  146. * @param {module:engine/model/range~Range} otherRange Range to differentiate against.
  147. * @returns {Array.<module:engine/model/range~Range>} The difference between ranges.
  148. */
  149. getDifference( otherRange ) {
  150. const ranges = [];
  151. if ( this.isIntersecting( otherRange ) ) {
  152. // Ranges intersect.
  153. if ( this.containsPosition( otherRange.start ) ) {
  154. // Given range start is inside this range. This means that we have to
  155. // add shrunken range - from the start to the middle of this range.
  156. ranges.push( new Range( this.start, otherRange.start ) );
  157. }
  158. if ( this.containsPosition( otherRange.end ) ) {
  159. // Given range end is inside this range. This means that we have to
  160. // add shrunken range - from the middle of this range to the end.
  161. ranges.push( new Range( otherRange.end, this.end ) );
  162. }
  163. } else {
  164. // Ranges do not intersect, return the original range.
  165. ranges.push( Range.createFromRange( this ) );
  166. }
  167. return ranges;
  168. }
  169. /**
  170. * Returns an intersection of this {@link ~Range range} and given {@link ~Range range}.
  171. * Intersection is a common part of both of those ranges. If ranges has no common part, returns `null`.
  172. *
  173. * Examples:
  174. *
  175. * let range = new Range( new Position( root, [ 2, 7 ] ), new Position( root, [ 4, 0, 1 ] ) );
  176. * let otherRange = new Range( new Position( root, [ 1 ] ), new Position( root, [ 2 ] ) );
  177. * let transformed = range.getIntersection( otherRange ); // null - ranges have no common part
  178. *
  179. * otherRange = new Range( new Position( root, [ 3 ] ), new Position( root, [ 5 ] ) );
  180. * transformed = range.getIntersection( otherRange ); // range from [ 3 ] to [ 4, 0, 1 ]
  181. *
  182. * @param {module:engine/model/range~Range} otherRange Range to check for intersection.
  183. * @returns {module:engine/model/range~Range|null} A common part of given ranges or `null` if ranges have no common part.
  184. */
  185. getIntersection( otherRange ) {
  186. if ( this.isIntersecting( otherRange ) ) {
  187. // Ranges intersect, so a common range will be returned.
  188. // At most, it will be same as this range.
  189. let commonRangeStart = this.start;
  190. let commonRangeEnd = this.end;
  191. if ( this.containsPosition( otherRange.start ) ) {
  192. // Given range start is inside this range. This means thaNt we have to
  193. // shrink common range to the given range start.
  194. commonRangeStart = otherRange.start;
  195. }
  196. if ( this.containsPosition( otherRange.end ) ) {
  197. // Given range end is inside this range. This means that we have to
  198. // shrink common range to the given range end.
  199. commonRangeEnd = otherRange.end;
  200. }
  201. return new Range( commonRangeStart, commonRangeEnd );
  202. }
  203. // Ranges do not intersect, so they do not have common part.
  204. return null;
  205. }
  206. /**
  207. * Computes and returns the smallest set of {@link #isFlat flat} ranges, that covers this range in whole.
  208. *
  209. * See an example of a model structure (`[` and `]` are range boundaries):
  210. *
  211. * root root
  212. * |- element DIV DIV P2 P3 DIV
  213. * | |- element H H P1 f o o b a r H P4
  214. * | | |- "fir[st" fir[st lorem se]cond ipsum
  215. * | |- element P1
  216. * | | |- "lorem" ||
  217. * |- element P2 ||
  218. * | |- "foo" VV
  219. * |- element P3
  220. * | |- "bar" root
  221. * |- element DIV DIV [P2 P3] DIV
  222. * | |- element H H [P1] f o o b a r H P4
  223. * | | |- "se]cond" fir[st] lorem [se]cond ipsum
  224. * | |- element P4
  225. * | | |- "ipsum"
  226. *
  227. * As it can be seen, letters contained in the range are: `stloremfoobarse`, spread across different parents.
  228. * We are looking for minimal set of flat ranges that contains the same nodes.
  229. *
  230. * Minimal flat ranges for above range `( [ 0, 0, 3 ], [ 3, 0, 2 ] )` will be:
  231. *
  232. * ( [ 0, 0, 3 ], [ 0, 0, 5 ] ) = "st"
  233. * ( [ 0, 1 ], [ 0, 2 ] ) = element P1 ("lorem")
  234. * ( [ 1 ], [ 3 ] ) = element P2, element P3 ("foobar")
  235. * ( [ 3, 0, 0 ], [ 3, 0, 2 ] ) = "se"
  236. *
  237. * **Note:** if an {@link module:engine/model/element~Element element} is not wholly contained in this range, it won't be returned
  238. * in any of the returned flat ranges. See in the example how `H` elements at the beginning and at the end of the range
  239. * were omitted. Only their parts that were wholly in the range were returned.
  240. *
  241. * **Note:** this method is not returning flat ranges that contain no nodes.
  242. *
  243. * @returns {Array.<module:engine/model/range~Range>} Array of flat ranges covering this range.
  244. */
  245. getMinimalFlatRanges() {
  246. const ranges = [];
  247. const diffAt = this.start.getCommonPath( this.end ).length;
  248. let pos = Position.createFromPosition( this.start );
  249. let posParent = pos.parent;
  250. // Go up.
  251. while ( pos.path.length > diffAt + 1 ) {
  252. let howMany = posParent.maxOffset - pos.offset;
  253. if ( howMany !== 0 ) {
  254. ranges.push( new Range( pos, pos.getShiftedBy( howMany ) ) );
  255. }
  256. pos.path = pos.path.slice( 0, -1 );
  257. pos.offset++;
  258. posParent = posParent.parent;
  259. }
  260. // Go down.
  261. while ( pos.path.length <= this.end.path.length ) {
  262. let offset = this.end.path[ pos.path.length - 1 ];
  263. let howMany = offset - pos.offset;
  264. if ( howMany !== 0 ) {
  265. ranges.push( new Range( pos, pos.getShiftedBy( howMany ) ) );
  266. }
  267. pos.offset = offset;
  268. pos.path.push( 0 );
  269. }
  270. return ranges;
  271. }
  272. /**
  273. * Creates a {@link module:engine/model/treewalker~TreeWalker TreeWalker} instance with this range as a boundary.
  274. *
  275. * @param {Object} options Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}.
  276. * @param {module:engine/model/position~Position} [options.startPosition]
  277. * @param {Boolean} [options.singleCharacters=false]
  278. * @param {Boolean} [options.shallow=false]
  279. * @param {Boolean} [options.ignoreElementEnd=false]
  280. */
  281. getWalker( options = {} ) {
  282. options.boundaries = this;
  283. return new TreeWalker( options );
  284. }
  285. /**
  286. * Returns an iterator that iterates over all {@link module:engine/model/item~Item items} that are in this range and returns
  287. * them.
  288. *
  289. * This method uses {@link module:engine/model/treewalker~TreeWalker} with `boundaries` set to this range and `ignoreElementEnd` option
  290. * set to `true`. However it returns only {@link module:engine/model/item~Item model items},
  291. * not {@link module:engine/model/treewalker~TreeWalkerValue}.
  292. *
  293. * You may specify additional options for the tree walker. See {@link module:engine/model/treewalker~TreeWalker} for
  294. * a full list of available options.
  295. *
  296. * @method getItems
  297. * @param {Object} options Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}.
  298. * @returns {Iterable.<module:engine/model/item~Item>}
  299. */
  300. *getItems( options = {} ) {
  301. options.boundaries = this;
  302. options.ignoreElementEnd = true;
  303. const treeWalker = new TreeWalker( options );
  304. for ( let value of treeWalker ) {
  305. yield value.item;
  306. }
  307. }
  308. /**
  309. * Returns an iterator that iterates over all {@link module:engine/model/position~Position positions} that are boundaries or
  310. * contained in this range.
  311. *
  312. * This method uses {@link module:engine/model/treewalker~TreeWalker} with `boundaries` set to this range. However it returns only
  313. * {@link module:engine/model/position~Position positions}, not {@link module:engine/model/treewalker~TreeWalkerValue}.
  314. *
  315. * You may specify additional options for the tree walker. See {@link module:engine/model/treewalker~TreeWalker} for
  316. * a full list of available options.
  317. *
  318. * @param {Object} options Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}.
  319. * @returns {Iterable.<module:engine/model/position~Position>}
  320. */
  321. *getPositions( options = {} ) {
  322. options.boundaries = this;
  323. const treeWalker = new TreeWalker( options );
  324. yield treeWalker.position;
  325. for ( let value of treeWalker ) {
  326. yield value.nextPosition;
  327. }
  328. }
  329. /**
  330. * Returns an array containing one or two {@link ~Range ranges} that are a result of transforming this
  331. * {@link ~Range range} by inserting `howMany` nodes at `insertPosition`. Two {@link ~Range ranges} are
  332. * returned if the insertion was inside this {@link ~Range range} and `spread` is set to `true`.
  333. *
  334. * Examples:
  335. *
  336. * let range = new Range( new Position( root, [ 2, 7 ] ), new Position( root, [ 4, 0, 1 ] ) );
  337. * let transformed = range._getTransformedByInsertion( new Position( root, [ 1 ] ), 2 );
  338. * // transformed array has one range from [ 4, 7 ] to [ 6, 0, 1 ]
  339. *
  340. * transformed = range._getTransformedByInsertion( new Position( root, [ 4, 0, 0 ] ), 4 );
  341. * // transformed array has one range from [ 2, 7 ] to [ 4, 0, 5 ]
  342. *
  343. * transformed = range._getTransformedByInsertion( new Position( root, [ 3, 2 ] ), 4 );
  344. * // transformed array has one range, which is equal to original range
  345. *
  346. * transformed = range._getTransformedByInsertion( new Position( root, [ 3, 2 ] ), 4, true );
  347. * // transformed array has two ranges: from [ 2, 7 ] to [ 3, 2 ] and from [ 3, 6 ] to [ 4, 0, 1 ]
  348. *
  349. * transformed = range._getTransformedByInsertion( new Position( root, [ 4, 0, 1 ] ), 4, false, false );
  350. * // transformed array has one range which is equal to original range because insertion is after the range boundary
  351. *
  352. * transformed = range._getTransformedByInsertion( new Position( root, [ 4, 0, 1 ] ), 4, false, true );
  353. * // transformed array has one range: from [ 2, 7 ] to [ 4, 0, 5 ] because range was expanded
  354. *
  355. * @protected
  356. * @param {module:engine/model/position~Position} insertPosition Position where nodes are inserted.
  357. * @param {Number} howMany How many nodes are inserted.
  358. * @param {Boolean} [spread] Flag indicating whether this {~Range range} should be spread if insertion
  359. * was inside the range. Defaults to `false`.
  360. * @param {Boolean} [isSticky] Flag indicating whether insertion should expand a range if it is in a place of
  361. * range boundary. Defaults to `false`.
  362. * @returns {Array.<module:engine/model/range~Range>} Result of the transformation.
  363. */
  364. _getTransformedByInsertion( insertPosition, howMany, spread = false, isSticky = false ) {
  365. if ( spread && this.containsPosition( insertPosition ) ) {
  366. // Range has to be spread. The first part is from original start to the spread point.
  367. // The other part is from spread point to the original end, but transformed by
  368. // insertion to reflect insertion changes.
  369. return [
  370. new Range( this.start, insertPosition ),
  371. new Range(
  372. insertPosition._getTransformedByInsertion( insertPosition, howMany, true ),
  373. this.end._getTransformedByInsertion( insertPosition, howMany, this.isCollapsed )
  374. )
  375. ];
  376. } else {
  377. const range = Range.createFromRange( this );
  378. let insertBeforeStart = range.isCollapsed ? isSticky : !isSticky;
  379. let insertBeforeEnd = isSticky;
  380. range.start = range.start._getTransformedByInsertion( insertPosition, howMany, insertBeforeStart );
  381. range.end = range.end._getTransformedByInsertion( insertPosition, howMany, insertBeforeEnd );
  382. return [ range ];
  383. }
  384. }
  385. /**
  386. * Returns an array containing {@link ~Range ranges} that are a result of transforming this
  387. * {@link ~Range range} by moving `howMany` nodes from `sourcePosition` to `targetPosition`.
  388. *
  389. * @protected
  390. * @param {module:engine/model/position~Position} sourcePosition Position from which nodes are moved.
  391. * @param {module:engine/model/position~Position} targetPosition Position to where nodes are moved.
  392. * @param {Number} howMany How many nodes are moved.
  393. * @param {Boolean} [spread] Flag indicating whether this {~Range range} should be spread if insertion
  394. * was inside the range. Defaults to `false`.
  395. * @returns {Array.<module:engine/model/range~Range>} Result of the transformation.
  396. */
  397. _getTransformedByMove( sourcePosition, targetPosition, howMany, spread, isSticky = false ) {
  398. if ( this.isCollapsed ) {
  399. const newPos = this.start._getTransformedByMove( sourcePosition, targetPosition, howMany, true, true );
  400. return [ new Range( newPos ) ];
  401. }
  402. let result;
  403. const moveRange = new Range( sourcePosition, sourcePosition.getShiftedBy( howMany ) );
  404. const differenceSet = this.getDifference( moveRange );
  405. let difference;
  406. if ( differenceSet.length == 1 ) {
  407. difference = new Range(
  408. differenceSet[ 0 ].start._getTransformedByDeletion( sourcePosition, howMany ),
  409. differenceSet[ 0 ].end._getTransformedByDeletion( sourcePosition, howMany )
  410. );
  411. } else if ( differenceSet.length == 2 ) {
  412. // This means that ranges were moved from the inside of this range.
  413. // So we can operate on this range positions and we don't have to transform starting position.
  414. difference = new Range(
  415. this.start,
  416. this.end._getTransformedByDeletion( sourcePosition, howMany )
  417. );
  418. } else {
  419. // 0.
  420. difference = null;
  421. }
  422. const insertPosition = targetPosition._getTransformedByDeletion( sourcePosition, howMany );
  423. if ( difference ) {
  424. result = difference._getTransformedByInsertion( insertPosition, howMany, spread, isSticky );
  425. } else {
  426. result = [];
  427. }
  428. const common = this.getIntersection( moveRange );
  429. // Add common part of the range only if there is any and only if it is not
  430. // already included in `difference` part.
  431. if ( common && ( spread || difference === null || !difference.containsPosition( insertPosition ) ) ) {
  432. result.push( new Range(
  433. common.start._getCombined( moveRange.start, insertPosition ),
  434. common.end._getCombined( moveRange.start, insertPosition )
  435. ) );
  436. }
  437. return result;
  438. }
  439. /**
  440. * Creates a new range, spreading from specified {@link module:engine/model/position~Position position} to a position moved by
  441. * given `shift`. If `shift` is a negative value, shifted position is treated as the beginning of the range.
  442. *
  443. * @param {module:engine/model/position~Position} position Beginning of the range.
  444. * @param {Number} shift How long the range should be.
  445. * @returns {module:engine/model/range~Range}
  446. */
  447. static createFromPositionAndShift( position, shift ) {
  448. const start = position;
  449. const end = position.getShiftedBy( shift );
  450. return shift > 0 ? new this( start, end ) : new this( end, start );
  451. }
  452. /**
  453. * Creates a range from given parents and offsets.
  454. *
  455. * @param {module:engine/model/element~Element} startElement Start position parent element.
  456. * @param {Number} startOffset Start position offset.
  457. * @param {module:engine/model/element~Element} endElement End position parent element.
  458. * @param {Number} endOffset End position offset.
  459. * @returns {module:engine/model/range~Range}
  460. */
  461. static createFromParentsAndOffsets( startElement, startOffset, endElement, endOffset ) {
  462. return new this(
  463. Position.createFromParentAndOffset( startElement, startOffset ),
  464. Position.createFromParentAndOffset( endElement, endOffset )
  465. );
  466. }
  467. /**
  468. * Creates a new instance of `Range` which is equal to passed range.
  469. *
  470. * @param {module:engine/model/range~Range} range Range to clone.
  471. * @returns {module:engine/model/range~Range}
  472. */
  473. static createFromRange( range ) {
  474. return new this( range.start, range.end );
  475. }
  476. /**
  477. * Creates a range inside an {@link module:engine/model/element~Element element} which starts before the first child of
  478. * that element and ends after the last child of that element.
  479. *
  480. * @param {module:engine/model/element~Element} element Element which is a parent for the range.
  481. * @returns {module:engine/model/range~Range}
  482. */
  483. static createIn( element ) {
  484. return this.createFromParentsAndOffsets( element, 0, element, element.maxOffset );
  485. }
  486. /**
  487. * Creates a range that starts before given {@link module:engine/model/item~Item model item} and ends after it.
  488. *
  489. * @param {module:engine/model/item~Item} item
  490. * @returns {module:engine/model/range~Range}
  491. */
  492. static createOn( item ) {
  493. return this.createFromPositionAndShift( Position.createBefore( item ), item.offsetSize );
  494. }
  495. /**
  496. * Creates a `Range` instance from given plain object (i.e. parsed JSON string).
  497. *
  498. * @param {Object} json Plain object to be converted to `Range`.
  499. * @param {module:engine/model/document~Document} doc Document object that will be range owner.
  500. * @returns {module:engine/model/element~Element} `Range` instance created using given plain object.
  501. */
  502. static fromJSON( json, doc ) {
  503. return new this( Position.fromJSON( json.start, doc ), Position.fromJSON( json.end, doc ) );
  504. }
  505. }