conversion.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module engine/conversion/conversion
  7. */
  8. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  9. import {
  10. downcastElementToElement,
  11. downcastAttributeToElement,
  12. downcastAttributeToAttribute
  13. } from './downcast-converters';
  14. import {
  15. upcastElementToElement,
  16. upcastElementToAttribute,
  17. upcastAttributeToAttribute
  18. } from './upcast-converters';
  19. /**
  20. * An utility class that helps adding converters to upcast and downcast dispatchers.
  21. *
  22. * We recommend reading first the {@glink framework/guides/architecture/editing-engine} guide to understand the
  23. * core concepts of the conversion mechanisms.
  24. *
  25. * The instance of the conversion manager is available in the
  26. * {@link module:core/editor/editor~Editor#conversion `editor.conversion`} property
  27. * and by default has the following groups of dispatchers (i.e. directions of conversion):
  28. *
  29. * * `downcast` (editing and data downcasts)
  30. * * `editingDowncast`
  31. * * `dataDowncast`
  32. * * `upcast`
  33. *
  34. * To add a converter to a specific group use the {@link module:engine/conversion/conversion~Conversion#for `for()`}
  35. * method:
  36. *
  37. * // Add a converter to editing downcast and data downcast.
  38. * editor.conversion.for( 'downcast' ).add( downcastElementToElement( config ) );
  39. *
  40. * // Add a converter to the data pipepline only:
  41. * editor.conversion.for( 'dataDowncast' ).add( downcastElementToElement( config ) );
  42. * // And a slightly different one for the editing pipeline:
  43. * editor.conversion.for( 'editingDowncast' ).add( downcastElementToWidget( config ) );
  44. *
  45. * The functions used in `add()` calls are one-way converters (i.e. you need to remember yourself to add
  46. * a converter in the other direction, if you feature requires that). They are also called "conversion helpers".
  47. * You can find a set of them in the {@link module:engine/conversion/downcast-converters} and
  48. * {@link module:engine/conversion/upcast-converters} modules
  49. *
  50. * Besides allowing to register converters to specific dispatchers, you can also use methods available in this
  51. * class to add two-way converters (upcast and downcast):
  52. *
  53. * * {@link module:engine/conversion/conversion~Conversion#elementToElement `elementToElement()`} –
  54. * model element to view element and vice versa
  55. * * {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement()`} –
  56. * model attribute to view element and vice versa
  57. * * {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement()`} –
  58. * model attribute to view element and vice versa
  59. */
  60. export default class Conversion {
  61. /**
  62. * Creates new Conversion instance.
  63. */
  64. constructor() {
  65. /**
  66. * @private
  67. * @member {Map}
  68. */
  69. this._dispatchersGroups = new Map();
  70. }
  71. /**
  72. * Registers one or more converters under given group name. Then, group name can be used to assign a converter
  73. * to multiple dispatchers at once.
  74. *
  75. * If given group name is used for a second time,
  76. * {@link module:utils/ckeditorerror~CKEditorError conversion-register-group-exists} error is thrown.
  77. *
  78. * @param {String} groupName A name for dispatchers group.
  79. * @param {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
  80. * module:engine/conversion/upcastdispatcher~UpcastDispatcher>} dispatchers Dispatchers to register
  81. * under given name.
  82. */
  83. register( groupName, dispatchers ) {
  84. if ( this._dispatchersGroups.has( groupName ) ) {
  85. /**
  86. * Trying to register a group name that was already registered.
  87. *
  88. * @error conversion-register-group-exists
  89. */
  90. throw new CKEditorError( 'conversion-register-group-exists: Trying to register a group name that was already registered.' );
  91. }
  92. this._dispatchersGroups.set( groupName, dispatchers );
  93. }
  94. /**
  95. * Provides chainable API to assign converters to dispatchers registered under given group name. Converters are added
  96. * by calling `.add()` method of an object returned by this function.
  97. *
  98. * conversion.for( 'downcast' )
  99. * .add( conversionHelperA )
  100. * .add( conversionHelperB );
  101. *
  102. * In above example, `conversionHelperA` and `conversionHelperB` will be called for all dispatchers from `'model'` group.
  103. *
  104. * `.add()` takes exactly one parameter, which is a function. That function should accept one parameter, which
  105. * is a dispatcher instance. The function should add an actual converter to passed dispatcher instance.
  106. *
  107. * Conversion helpers for most common cases are already provided. They are flexible enough to cover most use cases.
  108. * See documentation to learn how they can be configured.
  109. *
  110. * For downcast (model to view conversion), these are:
  111. *
  112. * * {@link module:engine/conversion/downcast-converters~downcastElementToElement downcast element to element converter},
  113. * * {@link module:engine/conversion/downcast-converters~downcastAttributeToElement downcast attribute to element converter},
  114. * * {@link module:engine/conversion/downcast-converters~downcastAttributeToAttribute downcast attribute to attribute converter}.
  115. *
  116. * For upcast (view to model conversion), these are:
  117. *
  118. * * {@link module:engine/conversion/upcast-converters~upcastElementToElement upcast element to element converter},
  119. * * {@link module:engine/conversion/upcast-converters~upcastElementToAttribute upcast attribute to element converter},
  120. * * {@link module:engine/conversion/upcast-converters~upcastAttributeToAttribute upcast attribute to attribute converter}.
  121. *
  122. * An example of using conversion helpers to convert `paragraph` model element to `p` view element (and back):
  123. *
  124. * // Define conversion configuration - model element 'paragraph' should be converted to view element 'p'.
  125. * const config = { model: 'paragraph', view: 'p' };
  126. *
  127. * // Add converters to proper dispatchers using conversion helpers.
  128. * conversion.for( 'downcast' ).add( downcastElementToElement( config ) );
  129. * conversion.for( 'upcast' ).add( upcastElementToElement( config ) );
  130. *
  131. * An example of providing custom conversion helper that uses custom converter function:
  132. *
  133. * // Adding custom `myConverter` converter for 'paragraph' element insertion, with default priority ('normal').
  134. * conversion.for( 'downcast' ).add( conversion.customConverter( 'insert:paragraph', myConverter ) );
  135. *
  136. * @param {String} groupName Name of dispatchers group to add converters to.
  137. * @returns {Object} Object with `.add()` method, providing a way to add converters.
  138. */
  139. for( groupName ) {
  140. const dispatchers = this._getDispatchers( groupName );
  141. return {
  142. add( conversionHelper ) {
  143. _addToDispatchers( dispatchers, conversionHelper );
  144. return this;
  145. }
  146. };
  147. }
  148. /**
  149. * Sets up converters between the model and the view which convert a model element to a view element (and vice versa).
  150. * For example, model `<paragraph>Foo</paragraph>` is `<p>Foo</p>` in the view.
  151. *
  152. * // Simple conversion from `paragraph` model element to `<p>` view element (and vice versa).
  153. * conversion.elementToElement( { model: 'paragraph', view: 'p' } );
  154. *
  155. * // Override other converters by specifying converter definition with higher priority.
  156. * conversion.elementToElement( { model: 'paragraph', view: 'div', converterPriority: 'high' } );
  157. *
  158. * // View specified as an object instead of a string.
  159. * conversion.elementToElement( {
  160. * model: 'fancyParagraph',
  161. * view: {
  162. * name: 'p',
  163. * classes: 'fancy'
  164. * }
  165. * } );
  166. *
  167. * // Use `upcastAlso` to define other view elements that should be also converted to `paragraph` element.
  168. * conversion.elementToElement( {
  169. * model: 'paragraph',
  170. * view: 'p',
  171. * upcastAlso: [
  172. * 'div',
  173. * {
  174. * // Any element with `display: block` style.
  175. * styles: {
  176. * display: 'block'
  177. * }
  178. * }
  179. * ]
  180. * } );
  181. *
  182. * // `upcastAlso` set as callback enables a conversion of a wide range of different view elements.
  183. * conversion.elementToElement( {
  184. * model: 'heading',
  185. * view: 'h2',
  186. * // Convert "headling-like" paragraphs to headings.
  187. * upcastAlso: viewElement => {
  188. * const fontSize = viewElement.getStyle( 'font-size' );
  189. *
  190. * if ( !fontSize ) {
  191. * return null;
  192. * }
  193. *
  194. * const match = fontSize.match( /(\d+)\s*px/ );
  195. *
  196. * if ( !match ) {
  197. * return null;
  198. * }
  199. *
  200. * const size = Number( match[ 1 ] );
  201. *
  202. * if ( size > 26 ) {
  203. * // Returned value be an object with the matched properties.
  204. * // Those properties will be "consumed" during conversion.
  205. * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more.
  206. *
  207. * return { name: true, styles: [ 'font-size' ] };
  208. * }
  209. *
  210. * return null;
  211. * }
  212. * } );
  213. *
  214. * `definition.model` is a `String` with a model element name to converter from/to.
  215. * See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.
  216. *
  217. * @param {module:engine/conversion/conversion~ConverterDefinition} definition Converter definition.
  218. */
  219. elementToElement( definition ) {
  220. // Set up downcast converter.
  221. this.for( 'downcast' ).add( downcastElementToElement( definition ) );
  222. // Set up upcast converter.
  223. for ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {
  224. this.for( 'upcast' ).add(
  225. upcastElementToElement( {
  226. model,
  227. view,
  228. converterPriority: definition.converterPriority
  229. } )
  230. );
  231. }
  232. }
  233. /**
  234. * Sets up converters between the model and the view which convert a model attribute to a view element (and vice versa).
  235. * For example, model text node with data `"Foo"` and `bold` attribute is `<strong>Foo</strong>` in the view.
  236. *
  237. * // Simple conversion from `bold=true` attribute to `<strong>` view element (and vice versa).
  238. * conversion.attributeToElement( { model: 'bold', view: 'strong' } );
  239. *
  240. * // Override other converters by specifying converter definition with higher priority.
  241. * conversion.attributeToElement( { model: 'bold', view: 'b', converterPriority: 'high' } );
  242. *
  243. * // View specified as an object instead of a string.
  244. * conversion.attributeToElement( {
  245. * model: 'bold',
  246. * view: {
  247. * name: 'span',
  248. * classes: 'bold'
  249. * }
  250. * } );
  251. *
  252. * // Use `config.model.name` to define conversion only from given node type, `$text` in this case.
  253. * // The same attribute on different elements may be then handled by a different converter.
  254. * conversion.attributeToElement( {
  255. * model: {
  256. * key: 'textDecoration',
  257. * values: [ 'underline', 'lineThrough' ],
  258. * name: '$text'
  259. * },
  260. * view: {
  261. * underline: {
  262. * name: 'span',
  263. * styles: {
  264. * 'text-decoration': 'underline'
  265. * }
  266. * },
  267. * lineThrough: {
  268. * name: 'span',
  269. * styles: {
  270. * 'text-decoration': 'line-through'
  271. * }
  272. * }
  273. * }
  274. * } );
  275. *
  276. * // Use `upcastAlso` to define other view elements that should be also converted to `bold` attribute.
  277. * conversion.attributeToElement( {
  278. * model: 'bold',
  279. * view: 'strong',
  280. * upcastAlso: [
  281. * 'b',
  282. * {
  283. * name: 'span',
  284. * classes: 'bold'
  285. * },
  286. * {
  287. * name: 'span',
  288. * styles: {
  289. * 'font-weight': 'bold'
  290. * }
  291. * },
  292. * viewElement => {
  293. * const fontWeight = viewElement.getStyle( 'font-weight' );
  294. *
  295. * if ( viewElement.is( 'span' ) && fontWeight && /\d+/.test() && Number( fontWeight ) > 500 ) {
  296. * // Returned value be an object with the matched properties.
  297. * // Those properties will be "consumed" during conversion.
  298. * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more.
  299. *
  300. * return {
  301. * name: true,
  302. * styles: [ 'font-weight' ]
  303. * };
  304. * }
  305. * }
  306. * ]
  307. * } );
  308. *
  309. * // Conversion from/to a model attribute key which value is an enum (`fontSize=big|small`).
  310. * // `upcastAlso` set as callback enables a conversion of a wide range of different view elements.
  311. * conversion.attributeToElement( {
  312. * model: {
  313. * key: 'fontSize',
  314. * values: [ 'big', 'small' ]
  315. * },
  316. * view: {
  317. * big: {
  318. * name: 'span',
  319. * styles: {
  320. * 'font-size': '1.2em'
  321. * }
  322. * },
  323. * small: {
  324. * name: 'span',
  325. * styles: {
  326. * 'font-size': '0.8em'
  327. * }
  328. * }
  329. * },
  330. * upcastAlso: {
  331. * big: viewElement => {
  332. * const fontSize = viewElement.getStyle( 'font-size' );
  333. *
  334. * if ( !fontSize ) {
  335. * return null;
  336. * }
  337. *
  338. * const match = fontSize.match( /(\d+)\s*px/ );
  339. *
  340. * if ( !match ) {
  341. * return null;
  342. * }
  343. *
  344. * const size = Number( match[ 1 ] );
  345. *
  346. * if ( viewElement.is( 'span' ) && size > 10 ) {
  347. * // Returned value be an object with the matched properties.
  348. * // Those properties will be "consumed" during conversion.
  349. * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more.
  350. *
  351. * return { name: true, styles: [ 'font-size' ] };
  352. * }
  353. *
  354. * return null;
  355. * },
  356. * small: viewElement => {
  357. * const fontSize = viewElement.getStyle( 'font-size' );
  358. *
  359. * if ( !fontSize ) {
  360. * return null;
  361. * }
  362. *
  363. * const match = fontSize.match( /(\d+)\s*px/ );
  364. *
  365. * if ( !match ) {
  366. * return null;
  367. * }
  368. *
  369. * const size = Number( match[ 1 ] );
  370. *
  371. * if ( viewElement.is( 'span' ) && size < 10 ) {
  372. * // Returned value be an object with the matched properties.
  373. * // Those properties will be "consumed" during conversion.
  374. * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more.
  375. *
  376. * return { name: true, styles: [ 'font-size' ] };
  377. * }
  378. *
  379. * return null;
  380. * }
  381. * }
  382. * } );
  383. *
  384. * `definition.model` parameter specifies what model attribute should be converted from/to. It can be a `{ key, value }` object
  385. * describing attribute key and value to convert or a `String` specifying just attribute key (then `value` is set to `true`).
  386. * See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.
  387. *
  388. * @param {module:engine/conversion/conversion~ConverterDefinition} definition Converter definition.
  389. */
  390. attributeToElement( definition ) {
  391. // Set up downcast converter.
  392. this.for( 'downcast' ).add( downcastAttributeToElement( definition ) );
  393. // Set up upcast converter.
  394. for ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {
  395. this.for( 'upcast' ).add(
  396. upcastElementToAttribute( {
  397. view,
  398. model,
  399. priority: definition.priority
  400. } )
  401. );
  402. }
  403. }
  404. /**
  405. * Sets up converters between the model and the view which convert a model attribute to a view attribute (and vice versa).
  406. * For example, `<image src='foo.jpg'></image>` is converted to `<img src='foo.jpg'></img>` (same attribute key and value).
  407. *
  408. * // Simple conversion from `source` model attribute to `src` view attribute (and vice versa).
  409. * conversion.attributeToAttribute( { model: 'source', view: 'src' } );
  410. *
  411. * // Attributes values are strictly specified.
  412. * conversion.attributeToAttribute( {
  413. * model: {
  414. * name: 'image',
  415. * key: 'aside',
  416. * values: [ 'aside' ]
  417. * },
  418. * view: {
  419. * aside: {
  420. * name: 'img',
  421. * key: 'class',
  422. * value: [ 'aside', 'half-size' ]
  423. * }
  424. * }
  425. * } );
  426. *
  427. * // Set style attribute.
  428. * conversion.attributeToAttribute( {
  429. * model: {
  430. * name: 'image',
  431. * key: 'aside',
  432. * values: [ 'aside' ]
  433. * },
  434. * view: {
  435. * aside: {
  436. * name: 'img',
  437. * key: 'style',
  438. * value: {
  439. * float: 'right',
  440. * width: '50%',
  441. * margin: '5px'
  442. * }
  443. * }
  444. * }
  445. * } );
  446. *
  447. * // Conversion from/to a model attribute key which value is an enum (`align=right|center`).
  448. * // Use `upcastAlso` to define other view elements that should be also converted to `align=right` attribute.
  449. * conversion.attributeToAttribute( {
  450. * model: {
  451. * key: 'align',
  452. * values: [ 'right', 'center' ]
  453. * },
  454. * view: {
  455. * right: {
  456. * key: 'class',
  457. * value: 'align-right'
  458. * },
  459. * center: {
  460. * key: 'class',
  461. * value: 'align-center'
  462. * }
  463. * },
  464. * upcastAlso: {
  465. * right: {
  466. * styles: {
  467. * 'text-align': 'right'
  468. * }
  469. * },
  470. * center: {
  471. * styles: {
  472. * 'text-align': 'center'
  473. * }
  474. * }
  475. * }
  476. * } );
  477. *
  478. * `definition.model` parameter specifies what model attribute should be converted from/to.
  479. * It can be a `{ key, [ values ], [ name ] }` object or a `String`, which will be treated like `{ key: definition.model }`.
  480. * `key` property is the model attribute key to convert from/to.
  481. * `values` are the possible model attribute values. If `values` is not set, model attribute value will be the same as the
  482. * view attribute value.
  483. * If `name` is set, conversion will be set up only for model elements with the given name.
  484. *
  485. * `definition.view` parameter specifies what view attribute should be converted from/to.
  486. * It can be a `{ key, value, [ name ] }` object or a `String`, which will be treated like `{ key: definition.view }`.
  487. * `key` property is the view attribute key to convert from/to.
  488. * `value` is the view attribute value to convert from/to. If `definition.value` is not set, view attribute value will be
  489. * the same as the model attribute value.
  490. * If `key` is `'class'`, `value` can be a `String` or an array of `String`s.
  491. * If `key` is `'style'`, `value` is an object with key-value pairs.
  492. * In other cases, `value` is a `String`.
  493. * If `name` is set, conversion will be set up only for model elements with the given name.
  494. * If `definition.model.values` is set, `definition.view` is an object which assigns values from `definition.model.values`
  495. * to `{ key, value, [ name ] }` objects.
  496. *
  497. * `definition.upcastAlso` specifies which other matching view elements should be also upcast to given model configuration.
  498. * If `definition.model.values` is set, `definition.upcastAlso` should be an object assigning values from `definition.model.values`
  499. * to {@link module:engine/view/matcher~MatcherPattern}s or arrays of {@link module:engine/view/matcher~MatcherPattern}s.
  500. *
  501. * **Note:** `definition.model` and `definition.view` form should be mirrored, that is the same type of parameters should
  502. * be given in both parameters.
  503. *
  504. * @param {Object} definition Converter definition.
  505. * @param {String|Object} definition.model Model attribute to convert from/to.
  506. * @param {String|Object} definition.view View attribute to convert from/to.
  507. * @param {module:engine/view/matcher~MatcherPattern|Array.<module:engine/view/matcher~MatcherPattern>} [definition.upcastAlso]
  508. * Any view element matching `definition.upcastAlso` will also be converted to the given model attribute. `definition.upcastAlso`
  509. * is used only if `config.model.values` is specified.
  510. */
  511. attributeToAttribute( definition ) {
  512. // Set up downcast converter.
  513. this.for( 'downcast' ).add( downcastAttributeToAttribute( definition ) );
  514. // Set up upcast converter.
  515. for ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {
  516. this.for( 'upcast' ).add(
  517. upcastAttributeToAttribute( {
  518. view,
  519. model
  520. } )
  521. );
  522. }
  523. }
  524. /**
  525. * Returns dispatchers registered under given group name.
  526. *
  527. * If given group name has not been registered,
  528. * {@link module:utils/ckeditorerror~CKEditorError conversion-for-unknown-group} error is thrown.
  529. *
  530. * @private
  531. * @param {String} groupName
  532. * @returns {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
  533. * module:engine/conversion/upcastdispatcher~UpcastDispatcher>}
  534. */
  535. _getDispatchers( groupName ) {
  536. const dispatchers = this._dispatchersGroups.get( groupName );
  537. if ( !dispatchers ) {
  538. /**
  539. * Trying to add a converter to an unknown dispatchers group.
  540. *
  541. * @error conversion-for-unknown-group
  542. */
  543. throw new CKEditorError( 'conversion-for-unknown-group: Trying to add a converter to an unknown dispatchers group.' );
  544. }
  545. return dispatchers;
  546. }
  547. }
  548. /**
  549. * Defines how the model should be converted from/to the view.
  550. *
  551. * @typedef {Object} module:engine/conversion/conversion~ConverterDefinition
  552. *
  553. * @property {*} [model] Model conversion definition. Describes model element or model attribute to convert. This parameter differs
  554. * for different functions that accepts `ConverterDefinition`. See the description of a function to learn how to set it.
  555. * @property {module:engine/view/elementdefinition~ElementDefinition|Object} view Definition of a view element to convert from/to.
  556. * If `model` describes multiple values, `view` is an object that assigns those values (`view` object keys) to view element definitions
  557. * (`view` object values).
  558. * @property {module:engine/view/matcher~MatcherPattern|Array.<module:engine/view/matcher~MatcherPattern>} [upcastAlso]
  559. * Any view element matching `upcastAlso` will also be converted to model. If `model` describes multiple values, `upcastAlso`
  560. * is an object that assigns those values (`upcastAlso` object keys) to {@link module:engine/view/matcher~MatcherPattern}s
  561. * (`upcastAlso` object values).
  562. * @property {module:utils/priorities~PriorityString} [converterPriority] Converter priority.
  563. */
  564. // Helper function for `Conversion` `.add()` method.
  565. //
  566. // Calls `conversionHelper` on each dispatcher from the group specified earlier in `.for()` call, effectively
  567. // adding converters to all specified dispatchers.
  568. //
  569. // @private
  570. // @param {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
  571. // module:engine/conversion/upcastdispatcher~UpcastDispatcher>} dispatchers
  572. // @param {Function} conversionHelper
  573. function _addToDispatchers( dispatchers, conversionHelper ) {
  574. for ( const dispatcher of dispatchers ) {
  575. conversionHelper( dispatcher );
  576. }
  577. }
  578. // Helper function that creates a joint array out of an item passed in `definition.view` and items passed in
  579. // `definition.upcastAlso`.
  580. //
  581. // @param {module:engine/conversion/conversion~ConverterDefinition} definition
  582. // @returns {Array} Array containing view definitions.
  583. function* _getAllUpcastDefinitions( definition ) {
  584. if ( definition.model.values ) {
  585. for ( const value of definition.model.values ) {
  586. const model = { key: definition.model.key, value };
  587. const view = definition.view[ value ];
  588. const upcastAlso = definition.upcastAlso ? definition.upcastAlso[ value ] : undefined;
  589. yield* _getUpcastDefinition( model, view, upcastAlso );
  590. }
  591. } else {
  592. yield* _getUpcastDefinition( definition.model, definition.view, definition.upcastAlso );
  593. }
  594. }
  595. function* _getUpcastDefinition( model, view, upcastAlso ) {
  596. yield { model, view };
  597. if ( upcastAlso ) {
  598. upcastAlso = Array.isArray( upcastAlso ) ? upcastAlso : [ upcastAlso ];
  599. for ( const upcastAlsoItem of upcastAlso ) {
  600. yield { model, view: upcastAlsoItem };
  601. }
  602. }
  603. }