upcastwriter.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module module:engine/view/upcastwriter
  7. */
  8. import Element from './element';
  9. import { isPlainObject } from 'lodash-es';
  10. import Position from './position';
  11. import Range from './range';
  12. import Selection from './selection';
  13. /**
  14. * View upcast writer.
  15. *
  16. * It provides a set of methods used to manipulate view nodes.
  17. * It should be used only while working on a non-semantic view
  18. * (e.g. a view created from HTML string on paste).
  19. * To manipulate a view which was or is being downcasted from the the model use the
  20. * {@link module:engine/view/downcastwriter~DowncastWriter downcast writer}.
  21. */
  22. export default class UpcastWriter {
  23. /**
  24. * Clones the provided element.
  25. *
  26. * @see module:engine/view/element~Element#_clone
  27. * @param {module:engine/view/element~Element} element Element to be cloned.
  28. * @param {Boolean} [deep=false] If set to `true` clones element and all its children recursively. When set to `false`,
  29. * element will be cloned without any children.
  30. * @returns {module:engine/view/element~Element} Clone of this element.
  31. */
  32. clone( element, deep = false ) {
  33. return element._clone( deep );
  34. }
  35. /**
  36. * Appends a child node or a list of child nodes at the end of this node
  37. * and sets the parent of these nodes to this element.
  38. *
  39. * @see module:engine/view/element~Element#_appendChild
  40. * @param {module:engine/view/item~Item|Iterable.<module:engine/view/item~Item>} items Items to be inserted.
  41. * @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} element Element
  42. * to which items will be appended.
  43. * @fires module:engine/view/node~Node#event:change
  44. * @returns {Number} Number of appended nodes.
  45. */
  46. appendChild( items, element ) {
  47. return element._appendChild( items );
  48. }
  49. /**
  50. * Inserts a child node or a list of child nodes on the given index and sets the parent of these nodes to
  51. * this element.
  52. *
  53. * @see module:engine/view/element~Element#_insertChild
  54. * @param {Number} index Offset at which nodes should be inserted.
  55. * @param {module:engine/view/item~Item|Iterable.<module:engine/view/item~Item>} items Items to be inserted.
  56. * @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} element Element
  57. * to which items will be inserted.
  58. * @fires module:engine/view/node~Node#event:change
  59. * @returns {Number} Number of inserted nodes.
  60. */
  61. insertChild( index, items, element ) {
  62. return element._insertChild( index, items );
  63. }
  64. /**
  65. * Removes the given number of child nodes starting at the given index and set the parent of these nodes to `null`.
  66. *
  67. * @see module:engine/view/element~Element#_removeChildren
  68. * @param {Number} index Offset from which nodes will be removed.
  69. * @param {Number} howMany Number of nodes to remove.
  70. * @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} element Element
  71. * which children will be removed.
  72. * @fires module:engine/view/node~Node#event:change
  73. * @returns {Array.<module:engine/view/node~Node>} The array containing removed nodes.
  74. */
  75. removeChildren( index, howMany, element ) {
  76. return element._removeChildren( index, howMany );
  77. }
  78. /**
  79. * Removes given element from the view structure. Will not have effect on detached elements.
  80. *
  81. * @param {module:engine/view/element~Element} element Element which will be removed.
  82. * @returns {Array.<module:engine/view/node~Node>} The array containing removed nodes.
  83. */
  84. remove( element ) {
  85. const parent = element.parent;
  86. if ( parent ) {
  87. return this.removeChildren( parent.getChildIndex( element ), 1, parent );
  88. }
  89. return [];
  90. }
  91. /**
  92. * Replaces given element with the new one in the view structure. Will not have effect on detached elements.
  93. *
  94. * @param {module:engine/view/element~Element} oldElement Element which will be replaced.
  95. * @param {module:engine/view/element~Element} newElement Element which will be inserted in the place of the old element.
  96. * @returns {Boolean} Whether old element was successfully replaced.
  97. */
  98. replace( oldElement, newElement ) {
  99. const parent = oldElement.parent;
  100. if ( parent ) {
  101. const index = parent.getChildIndex( oldElement );
  102. this.removeChildren( index, 1, parent );
  103. this.insertChild( index, newElement, parent );
  104. return true;
  105. }
  106. return false;
  107. }
  108. /**
  109. * Renames element by creating a copy of a given element but with its name changed and then moving contents of the
  110. * old element to the new one.
  111. *
  112. * Since this function creates a new element and removes the given one, the new element is returned to keep reference.
  113. *
  114. * @param {String} newName New element name.
  115. * @param {module:engine/view/element~Element} element Element to be renamed.
  116. * @returns {module:engine/view/element~Element|null} New element or null if the old element
  117. * was not replaced (happens for detached elements).
  118. */
  119. rename( newName, element ) {
  120. const newElement = new Element( newName, element.getAttributes(), element.getChildren() );
  121. return this.replace( element, newElement ) ? newElement : null;
  122. }
  123. /**
  124. * Adds or overwrites element's attribute with a specified key and value.
  125. *
  126. * writer.setAttribute( linkElement, 'href', 'http://ckeditor.com' );
  127. *
  128. * @see module:engine/view/element~Element#_setAttribute
  129. * @param {String} key Attribute key.
  130. * @param {String} value Attribute value.
  131. * @param {module:engine/view/element~Element} element Element for which attribute will be set.
  132. */
  133. setAttribute( key, value, element ) {
  134. element._setAttribute( key, value );
  135. }
  136. /**
  137. * Removes attribute from the element.
  138. *
  139. * writer.removeAttribute( linkElement, 'href' );
  140. *
  141. * @see module:engine/view/element~Element#_removeAttribute
  142. * @param {String} key Attribute key.
  143. * @param {module:engine/view/element~Element} element Element from which attribute will be removed.
  144. */
  145. removeAttribute( key, element ) {
  146. element._removeAttribute( key );
  147. }
  148. /**
  149. * Adds specified class to the element.
  150. *
  151. * writer.addClass( linkElement, 'foo' );
  152. * writer.addClass( linkElement, [ 'foo', 'bar' ] );
  153. *
  154. * @see module:engine/view/element~Element#_addClass
  155. * @param {Array.<String>|String} className Single class name or array of class names which will be added.
  156. * @param {module:engine/view/element~Element} element Element for which class will be added.
  157. */
  158. addClass( className, element ) {
  159. element._addClass( className );
  160. }
  161. /**
  162. * Removes specified class from the element.
  163. *
  164. * writer.removeClass( linkElement, 'foo' );
  165. * writer.removeClass( linkElement, [ 'foo', 'bar' ] );
  166. *
  167. * @see module:engine/view/element~Element#_removeClass
  168. * @param {Array.<String>|String} className Single class name or array of class names which will be removed.
  169. * @param {module:engine/view/element~Element} element Element from which class will be removed.
  170. */
  171. removeClass( className, element ) {
  172. element._removeClass( className );
  173. }
  174. /**
  175. * Adds style to the element.
  176. *
  177. * writer.setStyle( element, 'color', 'red' );
  178. * writer.setStyle( element, {
  179. * color: 'red',
  180. * position: 'fixed'
  181. * } );
  182. *
  183. * @see module:engine/view/element~Element#_setStyle
  184. * @param {String|Object} property Property name or object with key - value pairs.
  185. * @param {String} [value] Value to set. This parameter is ignored if object is provided as the first parameter.
  186. * @param {module:engine/view/element~Element} element Element for which style will be added.
  187. */
  188. setStyle( property, value, element ) {
  189. if ( isPlainObject( property ) && element === undefined ) {
  190. element = value;
  191. }
  192. element._setStyle( property, value );
  193. }
  194. /**
  195. * Removes specified style from the element.
  196. *
  197. * writer.removeStyle( element, 'color' ); // Removes 'color' style.
  198. * writer.removeStyle( element, [ 'color', 'border-top' ] ); // Removes both 'color' and 'border-top' styles.
  199. *
  200. * @see module:engine/view/element~Element#_removeStyle
  201. * @param {Array.<String>|String} property Style property name or names to be removed.
  202. * @param {module:engine/view/element~Element} element Element from which style will be removed.
  203. */
  204. removeStyle( property, element ) {
  205. element._removeStyle( property );
  206. }
  207. /**
  208. * Sets a custom property on element. Unlike attributes, custom properties are not rendered to the DOM,
  209. * so they can be used to add special data to elements.
  210. *
  211. * @see module:engine/view/element~Element#_setCustomProperty
  212. * @param {String|Symbol} key Custom property name/key.
  213. * @param {*} value Custom property value to be stored.
  214. * @param {module:engine/view/element~Element} element Element for which custom property will be set.
  215. */
  216. setCustomProperty( key, value, element ) {
  217. element._setCustomProperty( key, value );
  218. }
  219. /**
  220. * Removes a custom property stored under the given key.
  221. *
  222. * @see module:engine/view/element~Element#_removeCustomProperty
  223. * @param {String|Symbol} key Name/key of the custom property to be removed.
  224. * @param {module:engine/view/element~Element} element Element from which the custom property will be removed.
  225. * @returns {Boolean} Returns true if property was removed.
  226. */
  227. removeCustomProperty( key, element ) {
  228. return element._removeCustomProperty( key );
  229. }
  230. /**
  231. * Creates position at the given location. The location can be specified as:
  232. *
  233. * * a {@link module:engine/view/position~Position position},
  234. * * parent element and offset (offset defaults to `0`),
  235. * * parent element and `'end'` (sets position at the end of that element),
  236. * * {@link module:engine/view/item~Item view item} and `'before'` or `'after'` (sets position before or after given view item).
  237. *
  238. * This method is a shortcut to other constructors such as:
  239. *
  240. * * {@link #createPositionBefore},
  241. * * {@link #createPositionAfter},
  242. *
  243. * @param {module:engine/view/item~Item|module:engine/model/position~Position} itemOrPosition
  244. * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
  245. * first parameter is a {@link module:engine/view/item~Item view item}.
  246. */
  247. createPositionAt( itemOrPosition, offset ) {
  248. return Position._createAt( itemOrPosition, offset );
  249. }
  250. /**
  251. * Creates a new position after given view item.
  252. *
  253. * @param {module:engine/view/item~Item} item View item after which the position should be located.
  254. * @returns {module:engine/view/position~Position}
  255. */
  256. createPositionAfter( item ) {
  257. return Position._createAfter( item );
  258. }
  259. /**
  260. * Creates a new position before given view item.
  261. *
  262. * @param {module:engine/view/item~Item} item View item before which the position should be located.
  263. * @returns {module:engine/view/position~Position}
  264. */
  265. createPositionBefore( item ) {
  266. return Position._createBefore( item );
  267. }
  268. /**
  269. * Creates a range spanning from `start` position to `end` position.
  270. *
  271. * **Note:** This factory method creates it's own {@link module:engine/view/position~Position} instances basing on passed values.
  272. *
  273. * @param {module:engine/view/position~Position} start Start position.
  274. * @param {module:engine/view/position~Position} [end] End position. If not set, range will be collapsed at `start` position.
  275. * @returns {module:engine/view/range~Range}
  276. */
  277. createRange( start, end ) {
  278. return new Range( start, end );
  279. }
  280. /**
  281. * Creates a range that starts before given {@link module:engine/view/item~Item view item} and ends after it.
  282. *
  283. * @param {module:engine/view/item~Item} item
  284. * @returns {module:engine/view/range~Range}
  285. */
  286. createRangeOn( item ) {
  287. return Range._createOn( item );
  288. }
  289. /**
  290. * Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
  291. * that element and ends after the last child of that element.
  292. *
  293. * @param {module:engine/view/element~Element} element Element which is a parent for the range.
  294. * @returns {module:engine/view/range~Range}
  295. */
  296. createRangeIn( element ) {
  297. return Range._createIn( element );
  298. }
  299. /**
  300. * Creates a new {@link module:engine/view/selection~Selection} instance.
  301. *
  302. * // Creates empty selection without ranges.
  303. * const selection = writer.createSelection();
  304. *
  305. * // Creates selection at the given range.
  306. * const range = writer.createRange( start, end );
  307. * const selection = writer.createSelection( range );
  308. *
  309. * // Creates selection at the given ranges
  310. * const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];
  311. * const selection = writer.createSelection( ranges );
  312. *
  313. * // Creates selection from the other selection.
  314. * const otherSelection = writer.createSelection();
  315. * const selection = writer.createSelection( otherSelection );
  316. *
  317. * // Creates selection from the document selection.
  318. * const selection = writer.createSelection( editor.editing.view.document.selection );
  319. *
  320. * // Creates selection at the given position.
  321. * const position = writer.createPositionFromPath( root, path );
  322. * const selection = writer.createSelection( position );
  323. *
  324. * // Creates collapsed selection at the position of given item and offset.
  325. * const paragraph = writer.createContainerElement( 'paragraph' );
  326. * const selection = writer.createSelection( paragraph, offset );
  327. *
  328. * // Creates a range inside an {@link module:engine/view/element~Element element} which starts before the
  329. * // first child of that element and ends after the last child of that element.
  330. * const selection = writer.createSelection( paragraph, 'in' );
  331. *
  332. * // Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends
  333. * // just after the item.
  334. * const selection = writer.createSelection( paragraph, 'on' );
  335. *
  336. * `Selection`'s constructor allow passing additional options (`backward`, `fake` and `label`) as the last argument.
  337. *
  338. * // Creates backward selection.
  339. * const selection = writer.createSelection( range, { backward: true } );
  340. *
  341. * Fake selection does not render as browser native selection over selected elements and is hidden to the user.
  342. * This way, no native selection UI artifacts are displayed to the user and selection over elements can be
  343. * represented in other way, for example by applying proper CSS class.
  344. *
  345. * Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
  346. * (and be properly handled by screen readers).
  347. *
  348. * // Creates fake selection with label.
  349. * const selection = writer.createSelection( range, { fake: true, label: 'foo' } );
  350. *
  351. * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection|
  352. * module:engine/view/position~Position|Iterable.<module:engine/view/range~Range>|module:engine/view/range~Range|
  353. * module:engine/view/item~Item|null} [selectable=null]
  354. * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Offset or place when selectable is an `Item`.
  355. * @param {Object} [options]
  356. * @param {Boolean} [options.backward] Sets this selection instance to be backward.
  357. * @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
  358. * @param {String} [options.label] Label for the fake selection.
  359. * @returns {module:engine/view/selection~Selection}
  360. */
  361. createSelection( selectable, placeOrOffset, options ) {
  362. return new Selection( selectable, placeOrOffset, options );
  363. }
  364. }