conversion.js 24 KB

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