conversion.js 25 KB

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