8
0

viewconsumable.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import isArray from '../../utils/lib/lodash/isArray.js';
  6. import CKEditorError from '../../utils/ckeditorerror.js';
  7. import ViewElement from '../view/element.js';
  8. import ViewText from '../view/text.js';
  9. import ViewDocumentFragment from '../view/documentfragment.js';
  10. /**
  11. * This is a private helper-class for {@link engine.conversion.ViewConsumable}.
  12. * It represents and manipulates consumable parts of a single {@link engine.view.Element}.
  13. *
  14. * @private
  15. * @memberOf engine.conversion
  16. */
  17. class ViewElementConsumables {
  18. /**
  19. * Creates ViewElementConsumables instance.
  20. */
  21. constructor() {
  22. /**
  23. * Flag indicating if name of the element can be consumed.
  24. *
  25. * @private
  26. * @member {Boolean} engine.conversion.ViewElementConsumables#_canConsumeName
  27. */
  28. this._canConsumeName = null;
  29. /**
  30. * Contains maps of element's consumables: attributes, classes and styles.
  31. *
  32. * @private
  33. * @member {Object} engine.conversion.ViewElementConsumables#_consumables
  34. */
  35. this._consumables = {
  36. attribute: new Map(),
  37. style: new Map(),
  38. class: new Map()
  39. };
  40. }
  41. /**
  42. * Adds consumable parts of the {@link engine.view.Element view element}.
  43. * Element's name itself can be marked to be consumed (when element's name is consumed its attributes, classes and
  44. * styles still could be consumed):
  45. *
  46. * consumables.add( { name: true } );
  47. *
  48. * Attributes classes and styles:
  49. *
  50. * consumables.add( { attribute: 'title', class: 'foo', style: 'color' } );
  51. * consumables.add( { attribute: [ 'title', 'name' ], class: [ 'foo', 'bar' ] );
  52. *
  53. * Throws {@link utils.CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
  54. * attribute is provided - it should be handled separately by providing `style` and `class` in consumables object.
  55. *
  56. * @param {Object} consumables Object describing which parts of the element can be consumed.
  57. * @param {Boolean} consumables.name If set to `true` element's name will be added as consumable.
  58. * @param {String|Array.<String>} consumables.attribute Attribute name or array of attribute names to add as consumable.
  59. * @param {String|Array.<String>} consumables.class Class name or array of class names to add as consumable.
  60. * @param {String|Array.<String>} consumables.style Style name or array of style names to add as consumable.
  61. */
  62. add( consumables ) {
  63. if ( consumables.name ) {
  64. this._canConsumeName = true;
  65. }
  66. for ( let type in this._consumables ) {
  67. if ( type in consumables ) {
  68. this._add( type, consumables[ type ] );
  69. }
  70. }
  71. }
  72. /**
  73. * Tests if parts of the {@link engine.view.Element view element} can be consumed.
  74. *
  75. * Element's name can be tested:
  76. *
  77. * consumables.test( { name: true } );
  78. *
  79. * Attributes classes and styles:
  80. *
  81. * consumables.test( { attribute: 'title', class: 'foo', style: 'color' } );
  82. * consumables.test( { attribute: [ 'title', 'name' ], class: [ 'foo', 'bar' ] );
  83. *
  84. * @param {Object} consumables Object describing which parts of the element should be tested.
  85. * @param {Boolean} consumables.name If set to `true` element's name will be tested.
  86. * @param {String|Array.<String>} consumables.attribute Attribute name or array of attribute names to test.
  87. * @param {String|Array.<String>} consumables.class Class name or array of class names to test.
  88. * @param {String|Array.<String>} consumables.style Style name or array of style names to test.
  89. * @returns {Boolean|null} `true` when all tested items can be consumed, `null` when even one of the items
  90. * was never marked for consumption and `false` when even one of the items was already consumed.
  91. */
  92. test( consumables ) {
  93. // Check if name can be consumed.
  94. if ( consumables.name && !this._canConsumeName ) {
  95. return this._canConsumeName;
  96. }
  97. for ( let type in this._consumables ) {
  98. if ( type in consumables ) {
  99. const value = this._test( type, consumables[ type ] );
  100. if ( value !== true ) {
  101. return value;
  102. }
  103. }
  104. }
  105. // Return true only if all can be consumed.
  106. return true;
  107. }
  108. /**
  109. * Consumes parts of {@link engine.view.Element view element}. This function does not check if consumable item
  110. * is already consumed - it consumes all consumable items provided.
  111. * Element's name can be consumed:
  112. *
  113. * consumables.consume( { name: true } );
  114. *
  115. * Attributes classes and styles:
  116. *
  117. * consumables.consume( { attribute: 'title', class: 'foo', style: 'color' } );
  118. * consumables.consume( { attribute: [ 'title', 'name' ], class: [ 'foo', 'bar' ] );
  119. *
  120. * @param {Object} consumables Object describing which parts of the element should be consumed.
  121. * @param {Boolean} consumables.name If set to `true` element's name will be consumed.
  122. * @param {String|Array.<String>} consumables.attribute Attribute name or array of attribute names to consume.
  123. * @param {String|Array.<String>} consumables.class Class name or array of class names to consume.
  124. * @param {String|Array.<String>} consumables.style Style name or array of style names to consume.
  125. */
  126. consume( consumables ) {
  127. if ( consumables.name ) {
  128. this._canConsumeName = false;
  129. }
  130. for ( let type in this._consumables ) {
  131. if ( type in consumables ) {
  132. this._consume( type, consumables[ type ] );
  133. }
  134. }
  135. }
  136. /**
  137. * Revert already consumed parts of {@link engine.view.Element view Element}, so they can be consumed once again.
  138. * Element's name can be reverted:
  139. *
  140. * consumables.revert( { name: true } );
  141. *
  142. * Attributes classes and styles:
  143. *
  144. * consumables.revert( { attribute: 'title', class: 'foo', style: 'color' } );
  145. * consumables.revert( { attribute: [ 'title', 'name' ], class: [ 'foo', 'bar' ] );
  146. *
  147. * @param {Object} consumables Object describing which parts of the element should be reverted.
  148. * @param {Boolean} consumables.name If set to `true` element's name will be reverted.
  149. * @param {String|Array.<String>} consumables.attribute Attribute name or array of attribute names to revert.
  150. * @param {String|Array.<String>} consumables.class Class name or array of class names to revert.
  151. * @param {String|Array.<String>} consumables.style Style name or array of style names to revert.
  152. */
  153. revert( consumables ) {
  154. if ( consumables.name ) {
  155. this._canConsumeName = true;
  156. }
  157. for ( let type in this._consumables ) {
  158. if ( type in consumables ) {
  159. this._revert( type, consumables[ type ] );
  160. }
  161. }
  162. }
  163. /**
  164. * Helper method that adds consumables of a given type: attribute, class or style.
  165. *
  166. * Throws {@link utils.CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
  167. * type is provided - it should be handled separately by providing actual style/class type.
  168. *
  169. * @private
  170. * @param {String} type Type of the consumable item: `attribute`, `class` or `style`.
  171. * @param {String|Array.<String>} item Consumable item or array of items.
  172. */
  173. _add( type, item ) {
  174. const items = isArray( item ) ? item : [ item ];
  175. const consumables = this._consumables[ type ];
  176. for ( let name of items ) {
  177. if ( type === 'attribute' && ( name === 'class' || name === 'style' ) ) {
  178. /**
  179. * Class and style attributes should be handled separately.
  180. *
  181. * @error viewconsumable-invalid-attribute
  182. */
  183. throw new CKEditorError( 'viewconsumable-invalid-attribute: Classes and styles should be handled separately.' );
  184. }
  185. consumables.set( name, true );
  186. }
  187. }
  188. /**
  189. * Helper method that tests consumables of a given type: attribute, class or style.
  190. *
  191. * @private
  192. * @param {String} type Type of the consumable item: `attribute`, `class` or `style`.
  193. * @param {String|Array.<String>} item Consumable item or array of items.
  194. * @returns {Boolean|null} Returns `true` if all items can be consumed, `null` when one of the items cannot be
  195. * consumed and `false` when one of the items is already consumed.
  196. */
  197. _test( type, item ) {
  198. const items = isArray( item ) ? item : [ item ];
  199. const consumables = this._consumables[ type ];
  200. for ( let name of items ) {
  201. if ( type === 'attribute' && ( name === 'class' || name === 'style' ) ) {
  202. // Check all classes/styles if class/style attribute is tested.
  203. const value = this._test( name, [ ...this._consumables[ name ].keys() ] );
  204. if ( value !== true ) {
  205. return value;
  206. }
  207. } else {
  208. const value = consumables.get( name );
  209. // Return null if attribute is not found.
  210. if ( value === undefined ) {
  211. return null;
  212. }
  213. if ( !value ) {
  214. return false;
  215. }
  216. }
  217. }
  218. return true;
  219. }
  220. /**
  221. * Helper method that consumes items of a given type: attribute, class or style.
  222. *
  223. * @private
  224. * @param {String} type Type of the consumable item: `attribute`, `class` or `style`.
  225. * @param {String|Array.<String>} item Consumable item or array of items.
  226. */
  227. _consume( type, item ) {
  228. const items = isArray( item ) ? item : [ item ];
  229. const consumables = this._consumables[ type ];
  230. for ( let name of items ) {
  231. if ( type === 'attribute' && ( name === 'class' || name === 'style' ) ) {
  232. // If class or style is provided for consumption - consume them all.
  233. this._consume( name, [ ...this._consumables[ name ].keys() ] );
  234. } else {
  235. consumables.set( name, false );
  236. }
  237. }
  238. }
  239. /**
  240. * Helper method that reverts items of a given type: attribute, class or style.
  241. *
  242. * @private
  243. * @param {String} type Type of the consumable item: `attribute`, `class` or , `style`.
  244. * @param {String|Array.<String>} item Consumable item or array of items.
  245. */
  246. _revert( type, item ) {
  247. const items = isArray( item ) ? item : [ item ];
  248. const consumables = this._consumables[ type ];
  249. for ( let name of items ) {
  250. if ( type === 'attribute' && ( name === 'class' || name === 'style' ) ) {
  251. // If class or style is provided for reverting - revert them all.
  252. this._revert( name, [ ...this._consumables[ name ].keys() ] );
  253. } else {
  254. const value = consumables.get( name );
  255. if ( value === false ) {
  256. consumables.set( name, true );
  257. }
  258. }
  259. }
  260. }
  261. }
  262. /**
  263. * Class used for handling consumption of view {@link engine.view.Element elements},
  264. * {@link engine.view.Text text nodes} and {@link engine.view.DocumentFragment document fragments}.
  265. * Element's name and its parts (attributes, classes and styles) can be consumed separately. Consuming an element's name
  266. * does not consume its attributes, classes and styles.
  267. * To add items for consumption use {@link engine.conversion.ViewConsumable#add add method}.
  268. * To test items use {@link engine.conversion.ViewConsumable#test test method}.
  269. * To consume items use {@link engine.conversion.ViewConsumable#consume consume method}.
  270. * To revert already consumed items use {@link engine.conversion.ViewConsumable#revert revert method}.
  271. *
  272. * viewConsumable.add( element, { name: true } ); // Adds element's name as ready to be consumed.
  273. * viewConsumable.add( textNode ); // Adds text node for consumption.
  274. * viewConsumable.add( docFragment ); // Adds document fragment for consumption.
  275. * viewConsumable.test( element, { name: true } ); // Tests if element's name can be consumed.
  276. * viewConsumable.test( textNode ); // Tests if text node can be consumed.
  277. * viewConsumable.test( docFragment ); // Tests if document fragment can be consumed.
  278. * viewConsumable.consume( element, { name: true } ); // Consume element's name.
  279. * viewConsumable.consume( textNode ); // Consume text node.
  280. * viewConsumable.consume( docFragment ); // Consume document fragment.
  281. * viewConsumable.revert( element, { name: true } ); // Revert already consumed element's name.
  282. * viewConsumable.revert( textNode ); // Revert already consumed text node.
  283. * viewConsumable.revert( docFragment ); // Revert already consumed document fragment.
  284. *
  285. * @memberOf engine.conversion
  286. */
  287. export default class ViewConsumable {
  288. /**
  289. * Creates new ViewConsumable.
  290. */
  291. constructor() {
  292. /**
  293. * Map of consumable elements. If {@link engine.view.Element element} is used as a key,
  294. * {@link engine.conversion.ViewElementConsumables ViewElementConsumables} instance is stored as value.
  295. * For {@link engine.view.Text text nodes} and {@link engine.view.DocumentFragment document fragments}
  296. * boolean value is stored as value.
  297. *
  298. * @protected
  299. * @member {Map.<engine.conversion.ViewElementConsumables|Boolean>} engine.conversion.ViewConsumable#_consumables
  300. */
  301. this._consumables = new Map();
  302. }
  303. /**
  304. * Adds {@link engine.view.Element view element}, {@link engine.view.Text text node} or
  305. * {@link engine.view.DocumentFragment document fragment} as ready to be consumed.
  306. *
  307. * viewConsumable.add( p, { name: true } ); // Adds element's name to consume.
  308. * viewConsumable.add( p, { attribute: 'name' } ); // Adds element's attribute.
  309. * viewConsumable.add( p, { class: 'foobar' } ); // Adds element's class.
  310. * viewConsumable.add( p, { style: 'color' } ); // Adds element's style
  311. * viewConsumable.add( p, { attribute: 'name', style: 'color' } ); // Adds attribute and style.
  312. * viewConsumable.add( p, { class: [ 'baz', 'bar' ] } ); // Multiple consumables can be provided.
  313. * viewConsumable.add( textNode ); // Adds text node to consume.
  314. * viewConsumable.add( docFragment ); // Adds document fragment to consume.
  315. *
  316. * Throws {@link utils.CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
  317. * attribute is provided - it should be handled separately by providing actual style/class.
  318. *
  319. * viewConsumable.add( p, { attribute: 'style' } ); // This call will throw an exception.
  320. * viewConsumable.add( p, { style: 'color' } ); // This is properly handled style.
  321. *
  322. * @param {engine.view.Element|engine.view.Text|engine.view.DocumentFragment} element
  323. * @param {Object} [consumables] Used only if first parameter is {@link engine.view.Element view element} instance.
  324. * @param {Boolean} consumables.name If set to true element's name will be included.
  325. * @param {String|Array.<String>} consumables.attribute Attribute name or array of attribute names.
  326. * @param {String|Array.<String>} consumables.class Class name or array of class names.
  327. * @param {String|Array.<String>} consumables.style Style name or array of style names.
  328. */
  329. add( element, consumables ) {
  330. let elementConsumables;
  331. // For text nodes and document fragments just mark them as consumable.
  332. if ( element instanceof ViewText || element instanceof ViewDocumentFragment ) {
  333. this._consumables.set( element, true );
  334. return;
  335. }
  336. // For elements create new ViewElementConsumables or update already existing one.
  337. if ( !this._consumables.has( element ) ) {
  338. elementConsumables = new ViewElementConsumables();
  339. this._consumables.set( element, elementConsumables );
  340. } else {
  341. elementConsumables = this._consumables.get( element );
  342. }
  343. elementConsumables.add( consumables );
  344. }
  345. /**
  346. * Tests if {@link engine.view.Element view element}, {@link engine.view.Text text node} or
  347. * {@link engine.view.DocumentFragment document fragment} can be consumed.
  348. * It returns `true` when all items included in method's call can be consumed. Returns `false` when
  349. * first already consumed item is found and `null` when first non-consumable item is found.
  350. *
  351. * viewConsumable.test( p, { name: true } ); // Tests element's name.
  352. * viewConsumable.test( p, { attribute: 'name' } ); // Tests attribute.
  353. * viewConsumable.test( p, { class: 'foobar' } ); // Tests class.
  354. * viewConsumable.test( p, { style: 'color' } ); // Tests style.
  355. * viewConsumable.test( p, { attribute: 'name', style: 'color' } ); // Tests attribute and style.
  356. * viewConsumable.test( p, { class: [ 'baz', 'bar' ] } ); // Multiple consumables can be tested.
  357. * viewConsumable.test( textNode ); // Tests text node.
  358. * viewConsumable.test( docFragment ); // Tests document fragment.
  359. *
  360. * Testing classes and styles as attribute will test if all added classes/styles can be consumed.
  361. *
  362. * viewConsumable.test( p, { attribute: 'class' } ); // Tests if all added classes can be consumed.
  363. * viewConsumable.test( p, { attribute: 'style' } ); // Tests if all added styles can be consumed.
  364. *
  365. * @param {engine.view.Element|engine.view.Text|engine.view.DocumentFragment} element
  366. * @param {Object} [consumables] Used only if first parameter is {@link engine.view.Element view element} instance.
  367. * @param {Boolean} consumables.name If set to true element's name will be included.
  368. * @param {String|Array.<String>} consumables.attribute Attribute name or array of attribute names.
  369. * @param {String|Array.<String>} consumables.class Class name or array of class names.
  370. * @param {String|Array.<String>} consumables.style Style name or array of style names.
  371. * @returns {Boolean|null} Returns `true` when all items included in method's call can be consumed. Returns `false`
  372. * when first already consumed item is found and `null` when first non-consumable item is found.
  373. */
  374. test( element, consumables ) {
  375. const elementConsumables = this._consumables.get( element );
  376. if ( elementConsumables === undefined ) {
  377. return null;
  378. }
  379. // For text nodes and document fragments return stored boolean value.
  380. if ( element instanceof ViewText || element instanceof ViewDocumentFragment ) {
  381. return elementConsumables;
  382. }
  383. // For elements test consumables object.
  384. return elementConsumables.test( consumables );
  385. }
  386. /**
  387. * Consumes {@link engine.view.Element view element}, {@link engine.view.Text text node} or
  388. * {@link engine.view.DocumentFragment document fragment}.
  389. * It returns `true` when all items included in method's call can be consumed, otherwise returns `false`.
  390. *
  391. * viewConsumable.consume( p, { name: true } ); // Consumes element's name.
  392. * viewConsumable.consume( p, { attribute: 'name' } ); // Consumes element's attribute.
  393. * viewConsumable.consume( p, { class: 'foobar' } ); // Consumes element's class.
  394. * viewConsumable.consume( p, { style: 'color' } ); // Consumes element's style.
  395. * viewConsumable.consume( p, { attribute: 'name', style: 'color' } ); // Consumes attribute and style.
  396. * viewConsumable.consume( p, { class: [ 'baz', 'bar' ] } ); // Multiple consumables can be consumed.
  397. * viewConsumable.consume( textNode ); // Consumes text node.
  398. * viewConsumable.consume( docFragment ); // Consumes document fragment.
  399. *
  400. * Consuming classes and styles as attribute will test if all added classes/styles can be consumed.
  401. *
  402. * viewConsumable.consume( p, { attribute: 'class' } ); // Consume only if all added classes can be consumed.
  403. * viewConsumable.consume( p, { attribute: 'style' } ); // Consume only if all added styles can be consumed.
  404. *
  405. * @param {engine.view.Element|engine.view.Text|engine.view.DocumentFragment} element
  406. * @param {Object} [consumables] Used only if first parameter is {@link engine.view.Element view element} instance.
  407. * @param {Boolean} consumables.name If set to true element's name will be included.
  408. * @param {String|Array.<String>} consumables.attribute Attribute name or array of attribute names.
  409. * @param {String|Array.<String>} consumables.class Class name or array of class names.
  410. * @param {String|Array.<String>} consumables.style Style name or array of style names.
  411. * @returns {Boolean} Returns `true` when all items included in method's call can be consumed,
  412. * otherwise returns `false`.
  413. */
  414. consume( element, consumables ) {
  415. if ( this.test( element, consumables ) ) {
  416. if ( element instanceof ViewText || element instanceof ViewDocumentFragment ) {
  417. // For text nodes and document fragments set value to false.
  418. this._consumables.set( element, false );
  419. } else {
  420. // For elements - consume consumables object.
  421. this._consumables.get( element ).consume( consumables );
  422. }
  423. return true;
  424. }
  425. return false;
  426. }
  427. /**
  428. * Reverts {@link engine.view.Element view element}, {@link engine.view.Text text node} or
  429. * {@link engine.view.DocumentFragment document fragment} so they can be consumed once again.
  430. * Method does not revert items that were never previously added for consumption, even if they are included in
  431. * method's call.
  432. *
  433. * viewConsumable.revert( p, { name: true } ); // Reverts element's name.
  434. * viewConsumable.revert( p, { attribute: 'name' } ); // Reverts element's attribute.
  435. * viewConsumable.revert( p, { class: 'foobar' } ); // Reverts element's class.
  436. * viewConsumable.revert( p, { style: 'color' } ); // Reverts element's style.
  437. * viewConsumable.revert( p, { attribute: 'name', style: 'color' } ); // Reverts attribute and style.
  438. * viewConsumable.revert( p, { class: [ 'baz', 'bar' ] } ); // Multiple names can be reverted.
  439. * viewConsumable.revert( textNode ); // Reverts text node.
  440. * viewConsumable.revert( docFragment ); // Reverts document fragment.
  441. *
  442. * Reverting classes and styles as attribute will revert all classes/styles that were previously added for
  443. * consumption.
  444. *
  445. * viewConsumable.revert( p, { attribute: 'class' } ); // Reverts all classes added for consumption.
  446. * viewConsumable.revert( p, { attribute: 'style' } ); // Reverts all styles added for consumption.
  447. *
  448. * @param {engine.view.Element|engine.view.Text|engine.view.DocumentFragment} element
  449. * @param {Object} [consumables] Used only if first parameter is {@link engine.view.Element view element} instance.
  450. * @param {Boolean} consumables.name If set to true element's name will be included.
  451. * @param {String|Array.<String>} consumables.attribute Attribute name or array of attribute names.
  452. * @param {String|Array.<String>} consumables.class Class name or array of class names.
  453. * @param {String|Array.<String>} consumables.style Style name or array of style names.
  454. */
  455. revert( element, consumables ) {
  456. const elementConsumables = this._consumables.get( element );
  457. if ( elementConsumables !== undefined ) {
  458. if ( element instanceof ViewText || element instanceof ViewDocumentFragment ) {
  459. // For text nodes and document fragments - set consumable to true.
  460. this._consumables.set( element, true );
  461. } else {
  462. // For elements - revert items from consumables object.
  463. elementConsumables.revert( consumables );
  464. }
  465. }
  466. }
  467. /**
  468. * Creates consumable object from {@link engine.view.Element view element}. Consumable object will include
  469. * element's name and all its attributes, classes and styles.
  470. *
  471. * @static
  472. * @param {engine.view.Element} element
  473. * @returns {Object} consumables
  474. */
  475. static consumablesFromElement( element ) {
  476. const consumables = {
  477. name: true,
  478. attribute: [],
  479. class: [],
  480. style: []
  481. };
  482. const attributes = element.getAttributeKeys();
  483. for ( let attribute of attributes ) {
  484. // Skip classes and styles - will be added separately.
  485. if ( attribute == 'style' || attribute == 'class' ) {
  486. continue;
  487. }
  488. consumables.attribute.push( attribute );
  489. }
  490. const classes = element.getClassNames();
  491. for ( let className of classes ) {
  492. consumables.class.push( className );
  493. }
  494. const styles = element.getStyleNames();
  495. for ( let style of styles ) {
  496. consumables.style.push( style );
  497. }
  498. return consumables;
  499. }
  500. /**
  501. * Creates {@link engine.conversion.ViewConsumable ViewConsumable} instance from
  502. * {@link engine.view.Element element} or {@link engine.view.DocumentFragment document fragment}.
  503. * Instance will contain all elements, child nodes, attributes, styles and classes added for consumption.
  504. *
  505. * @static
  506. * @param {engine.view.Element|engine.view.DocumentFragment} from View element or document fragment
  507. * from which `ViewConsumable` will be created.
  508. * @param {engine.conversion.ViewConsumable} [instance] If provided, given `ViewConsumable` instance will be used
  509. * to add all consumables. It will be returned instead of a new instance.
  510. */
  511. static createFrom( from, instance ) {
  512. if ( !instance ) {
  513. instance = new ViewConsumable();
  514. }
  515. if ( from instanceof ViewText ) {
  516. instance.add( from );
  517. return instance;
  518. }
  519. // Add `from` itself, if it is an element.
  520. if ( from instanceof ViewElement ) {
  521. instance.add( from, ViewConsumable.consumablesFromElement( from ) );
  522. }
  523. if ( from instanceof ViewDocumentFragment ) {
  524. instance.add( from );
  525. }
  526. for ( let child of from.getChildren() ) {
  527. instance = ViewConsumable.createFrom( child, instance );
  528. }
  529. return instance;
  530. }
  531. }