@ckeditor/ckeditor5-engine 34.2.0 → 35.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/CHANGELOG.md +823 -0
  2. package/LICENSE.md +4 -0
  3. package/package.json +32 -25
  4. package/src/controller/datacontroller.js +467 -561
  5. package/src/controller/editingcontroller.js +168 -204
  6. package/src/conversion/conversion.js +541 -565
  7. package/src/conversion/conversionhelpers.js +24 -28
  8. package/src/conversion/downcastdispatcher.js +457 -686
  9. package/src/conversion/downcasthelpers.js +1583 -1965
  10. package/src/conversion/mapper.js +518 -707
  11. package/src/conversion/modelconsumable.js +240 -283
  12. package/src/conversion/upcastdispatcher.js +372 -718
  13. package/src/conversion/upcasthelpers.js +707 -818
  14. package/src/conversion/viewconsumable.js +524 -581
  15. package/src/dataprocessor/basichtmlwriter.js +12 -16
  16. package/src/dataprocessor/dataprocessor.js +5 -0
  17. package/src/dataprocessor/htmldataprocessor.js +101 -117
  18. package/src/dataprocessor/htmlwriter.js +1 -18
  19. package/src/dataprocessor/xmldataprocessor.js +117 -138
  20. package/src/dev-utils/model.js +260 -352
  21. package/src/dev-utils/operationreplayer.js +106 -126
  22. package/src/dev-utils/utils.js +34 -51
  23. package/src/dev-utils/view.js +632 -753
  24. package/src/index.js +0 -11
  25. package/src/model/batch.js +111 -127
  26. package/src/model/differ.js +988 -1233
  27. package/src/model/document.js +340 -449
  28. package/src/model/documentfragment.js +327 -364
  29. package/src/model/documentselection.js +996 -1189
  30. package/src/model/element.js +306 -410
  31. package/src/model/history.js +224 -262
  32. package/src/model/item.js +5 -0
  33. package/src/model/liveposition.js +84 -145
  34. package/src/model/liverange.js +108 -185
  35. package/src/model/markercollection.js +379 -480
  36. package/src/model/model.js +883 -1034
  37. package/src/model/node.js +419 -463
  38. package/src/model/nodelist.js +175 -201
  39. package/src/model/operation/attributeoperation.js +153 -182
  40. package/src/model/operation/detachoperation.js +64 -83
  41. package/src/model/operation/insertoperation.js +135 -166
  42. package/src/model/operation/markeroperation.js +114 -140
  43. package/src/model/operation/mergeoperation.js +163 -191
  44. package/src/model/operation/moveoperation.js +157 -187
  45. package/src/model/operation/nooperation.js +28 -38
  46. package/src/model/operation/operation.js +106 -125
  47. package/src/model/operation/operationfactory.js +30 -34
  48. package/src/model/operation/renameoperation.js +109 -135
  49. package/src/model/operation/rootattributeoperation.js +155 -188
  50. package/src/model/operation/splitoperation.js +196 -232
  51. package/src/model/operation/transform.js +1833 -2204
  52. package/src/model/operation/utils.js +140 -204
  53. package/src/model/position.js +899 -1053
  54. package/src/model/range.js +910 -1028
  55. package/src/model/rootelement.js +77 -97
  56. package/src/model/schema.js +1189 -1835
  57. package/src/model/selection.js +745 -862
  58. package/src/model/text.js +90 -114
  59. package/src/model/textproxy.js +204 -240
  60. package/src/model/treewalker.js +316 -397
  61. package/src/model/typecheckable.js +16 -0
  62. package/src/model/utils/autoparagraphing.js +32 -44
  63. package/src/model/utils/deletecontent.js +334 -418
  64. package/src/model/utils/findoptimalinsertionrange.js +25 -36
  65. package/src/model/utils/getselectedcontent.js +96 -118
  66. package/src/model/utils/insertcontent.js +654 -773
  67. package/src/model/utils/insertobject.js +96 -119
  68. package/src/model/utils/modifyselection.js +120 -158
  69. package/src/model/utils/selection-post-fixer.js +153 -201
  70. package/src/model/writer.js +1305 -1474
  71. package/src/view/attributeelement.js +189 -225
  72. package/src/view/containerelement.js +75 -85
  73. package/src/view/document.js +172 -215
  74. package/src/view/documentfragment.js +200 -249
  75. package/src/view/documentselection.js +338 -367
  76. package/src/view/domconverter.js +1371 -1613
  77. package/src/view/downcastwriter.js +1747 -2076
  78. package/src/view/editableelement.js +81 -97
  79. package/src/view/element.js +739 -890
  80. package/src/view/elementdefinition.js +5 -0
  81. package/src/view/emptyelement.js +82 -92
  82. package/src/view/filler.js +35 -50
  83. package/src/view/item.js +5 -0
  84. package/src/view/matcher.js +260 -559
  85. package/src/view/node.js +274 -360
  86. package/src/view/observer/arrowkeysobserver.js +19 -28
  87. package/src/view/observer/bubblingemittermixin.js +120 -263
  88. package/src/view/observer/bubblingeventinfo.js +47 -55
  89. package/src/view/observer/clickobserver.js +7 -13
  90. package/src/view/observer/compositionobserver.js +14 -24
  91. package/src/view/observer/domeventdata.js +57 -67
  92. package/src/view/observer/domeventobserver.js +40 -64
  93. package/src/view/observer/fakeselectionobserver.js +81 -96
  94. package/src/view/observer/focusobserver.js +45 -61
  95. package/src/view/observer/inputobserver.js +7 -13
  96. package/src/view/observer/keyobserver.js +17 -27
  97. package/src/view/observer/mouseobserver.js +7 -14
  98. package/src/view/observer/mutationobserver.js +220 -315
  99. package/src/view/observer/observer.js +81 -102
  100. package/src/view/observer/selectionobserver.js +191 -246
  101. package/src/view/observer/tabobserver.js +23 -36
  102. package/src/view/placeholder.js +128 -173
  103. package/src/view/position.js +350 -401
  104. package/src/view/range.js +453 -513
  105. package/src/view/rawelement.js +85 -112
  106. package/src/view/renderer.js +874 -1014
  107. package/src/view/rooteditableelement.js +80 -90
  108. package/src/view/selection.js +608 -689
  109. package/src/view/styles/background.js +43 -44
  110. package/src/view/styles/border.js +220 -276
  111. package/src/view/styles/margin.js +8 -17
  112. package/src/view/styles/padding.js +8 -16
  113. package/src/view/styles/utils.js +127 -160
  114. package/src/view/stylesmap.js +728 -905
  115. package/src/view/text.js +102 -126
  116. package/src/view/textproxy.js +144 -170
  117. package/src/view/treewalker.js +383 -479
  118. package/src/view/typecheckable.js +19 -0
  119. package/src/view/uielement.js +166 -187
  120. package/src/view/upcastwriter.js +395 -449
  121. package/src/view/view.js +569 -664
  122. package/src/dataprocessor/dataprocessor.jsdoc +0 -64
  123. package/src/model/item.jsdoc +0 -14
  124. package/src/view/elementdefinition.jsdoc +0 -59
  125. package/src/view/item.jsdoc +0 -14
@@ -2,16 +2,13 @@
2
2
  * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module engine/conversion/conversion
8
7
  */
9
-
10
8
  import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
11
9
  import UpcastHelpers from './upcasthelpers';
12
10
  import DowncastHelpers from './downcasthelpers';
13
11
  import toArray from '@ckeditor/ckeditor5-utils/src/toarray';
14
-
15
12
  /**
16
13
  * A utility class that helps add converters to upcast and downcast dispatchers.
17
14
  *
@@ -57,547 +54,529 @@ import toArray from '@ckeditor/ckeditor5-utils/src/toarray';
57
54
  * Model attribute to view attribute and vice versa.
58
55
  */
59
56
  export default class Conversion {
60
- /**
61
- * Creates a new conversion instance.
62
- *
63
- * @param {module:engine/conversion/downcastdispatcher~DowncastDispatcher|
64
- * Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher>} downcastDispatchers
65
- * @param {module:engine/conversion/upcastdispatcher~UpcastDispatcher|
66
- * Array.<module:engine/conversion/upcastdispatcher~UpcastDispatcher>} upcastDispatchers
67
- */
68
- constructor( downcastDispatchers, upcastDispatchers ) {
69
- /**
70
- * Maps dispatchers group name to ConversionHelpers instances.
71
- *
72
- * @private
73
- * @member {Map.<String,module:engine/conversion/conversionhelpers~ConversionHelpers>}
74
- */
75
- this._helpers = new Map();
76
-
77
- // Define default 'downcast' & 'upcast' dispatchers groups. Those groups are always available as two-way converters needs them.
78
- this._downcast = toArray( downcastDispatchers );
79
- this._createConversionHelpers( { name: 'downcast', dispatchers: this._downcast, isDowncast: true } );
80
-
81
- this._upcast = toArray( upcastDispatchers );
82
- this._createConversionHelpers( { name: 'upcast', dispatchers: this._upcast, isDowncast: false } );
83
- }
84
-
85
- /**
86
- * Define an alias for registered dispatcher.
87
- *
88
- * const conversion = new Conversion(
89
- * [ dataDowncastDispatcher, editingDowncastDispatcher ],
90
- * upcastDispatcher
91
- * );
92
- *
93
- * conversion.addAlias( 'dataDowncast', dataDowncastDispatcher );
94
- *
95
- * @param {String} alias An alias of a dispatcher.
96
- * @param {module:engine/conversion/downcastdispatcher~DowncastDispatcher|
97
- * module:engine/conversion/upcastdispatcher~UpcastDispatcher} dispatcher Dispatcher which should have an alias.
98
- */
99
- addAlias( alias, dispatcher ) {
100
- const isDowncast = this._downcast.includes( dispatcher );
101
- const isUpcast = this._upcast.includes( dispatcher );
102
-
103
- if ( !isUpcast && !isDowncast ) {
104
- /**
105
- * Trying to register an alias for a dispatcher that nas not been registered.
106
- *
107
- * @error conversion-add-alias-dispatcher-not-registered
108
- */
109
- throw new CKEditorError(
110
- 'conversion-add-alias-dispatcher-not-registered',
111
- this
112
- );
113
- }
114
-
115
- this._createConversionHelpers( { name: alias, dispatchers: [ dispatcher ], isDowncast } );
116
- }
117
-
118
- /**
119
- * Provides a chainable API to assign converters to a conversion dispatchers group.
120
- *
121
- * If the given group name has not been registered, the
122
- * {@link module:utils/ckeditorerror~CKEditorError `conversion-for-unknown-group` error} is thrown.
123
- *
124
- * You can use conversion helpers available directly in the `for()` chain or your custom ones via
125
- * the {@link module:engine/conversion/conversionhelpers~ConversionHelpers#add `add()`} method.
126
- *
127
- * # Using built-in conversion helpers
128
- *
129
- * The `for()` chain comes with a set of conversion helpers which you can use like this:
130
- *
131
- * editor.conversion.for( 'downcast' )
132
- * .elementToElement( config1 ) // Adds an element-to-element downcast converter.
133
- * .attributeToElement( config2 ); // Adds an attribute-to-element downcast converter.
134
- *
135
- * editor.conversion.for( 'upcast' )
136
- * .elementToAttribute( config3 ); // Adds an element-to-attribute upcast converter.
137
- *
138
- * Refer to the documentation of built-in conversion helpers to learn about their configuration options.
139
- *
140
- * * downcast (model-to-view) conversion helpers:
141
- *
142
- * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`},
143
- * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement `attributeToElement()`},
144
- * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToAttribute `attributeToAttribute()`}.
145
- * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToElement `markerToElement()`}.
146
- * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToHighlight `markerToHighlight()`}.
147
- *
148
- * * upcast (view-to-model) conversion helpers:
149
- *
150
- * * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToElement `elementToElement()`},
151
- * * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToAttribute `elementToAttribute()`},
152
- * * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#attributeToAttribute `attributeToAttribute()`}.
153
- * * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToMarker `elementToMarker()`}.
154
- *
155
- * # Using custom conversion helpers
156
- *
157
- * If you need to implement an atypical converter, you can do so by calling:
158
- *
159
- * editor.conversion.for( direction ).add( customHelper );
160
- *
161
- * The `.add()` method takes exactly one parameter, which is a function. This function should accept one parameter that
162
- * is a dispatcher instance. The function should add an actual converter to the passed dispatcher instance.
163
- *
164
- * Example:
165
- *
166
- * editor.conversion.for( 'upcast' ).add( dispatcher => {
167
- * dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
168
- * // Do something with a view <a> element.
169
- * } );
170
- * } );
171
- *
172
- * Refer to the documentation of {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher}
173
- * and {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} to learn how to write
174
- * custom converters.
175
- *
176
- * @param {String} groupName The name of dispatchers group to add the converters to.
177
- * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers|module:engine/conversion/upcasthelpers~UpcastHelpers}
178
- */
179
- for( groupName ) {
180
- if ( !this._helpers.has( groupName ) ) {
181
- /**
182
- * Trying to add a converter to an unknown dispatchers group.
183
- *
184
- * @error conversion-for-unknown-group
185
- */
186
- throw new CKEditorError( 'conversion-for-unknown-group', this );
187
- }
188
-
189
- return this._helpers.get( groupName );
190
- }
191
-
192
- /**
193
- * Sets up converters between the model and the view that convert a model element to a view element (and vice versa).
194
- * For example, the model `<paragraph>Foo</paragraph>` is turned into `<p>Foo</p>` in the view.
195
- *
196
- * // A simple conversion from the `paragraph` model element to the `<p>` view element (and vice versa).
197
- * editor.conversion.elementToElement( { model: 'paragraph', view: 'p' } );
198
- *
199
- * // Override other converters by specifying a converter definition with a higher priority.
200
- * editor.conversion.elementToElement( { model: 'paragraph', view: 'div', converterPriority: 'high' } );
201
- *
202
- * // View specified as an object instead of a string.
203
- * editor.conversion.elementToElement( {
204
- * model: 'fancyParagraph',
205
- * view: {
206
- * name: 'p',
207
- * classes: 'fancy'
208
- * }
209
- * } );
210
- *
211
- * // Use `upcastAlso` to define other view elements that should also be converted to a `paragraph` element.
212
- * editor.conversion.elementToElement( {
213
- * model: 'paragraph',
214
- * view: 'p',
215
- * upcastAlso: [
216
- * 'div',
217
- * {
218
- * // Any element with the `display: block` style.
219
- * styles: {
220
- * display: 'block'
221
- * }
222
- * }
223
- * ]
224
- * } );
225
- *
226
- * // `upcastAlso` set as callback enables a conversion of a wide range of different view elements.
227
- * editor.conversion.elementToElement( {
228
- * model: 'heading',
229
- * view: 'h2',
230
- * // Convert "heading-like" paragraphs to headings.
231
- * upcastAlso: viewElement => {
232
- * const fontSize = viewElement.getStyle( 'font-size' );
233
- *
234
- * if ( !fontSize ) {
235
- * return null;
236
- * }
237
- *
238
- * const match = fontSize.match( /(\d+)\s*px/ );
239
- *
240
- * if ( !match ) {
241
- * return null;
242
- * }
243
- *
244
- * const size = Number( match[ 1 ] );
245
- *
246
- * if ( size > 26 ) {
247
- * // Returned value can be an object with the matched properties.
248
- * // These properties will be "consumed" during the conversion.
249
- * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
250
- *
251
- * return { name: true, styles: [ 'font-size' ] };
252
- * }
253
- *
254
- * return null;
255
- * }
256
- * } );
257
- *
258
- * `definition.model` is a `String` with a model element name to convert from or to.
259
- * See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.
260
- *
261
- * @param {module:engine/conversion/conversion~ConverterDefinition} definition The converter definition.
262
- */
263
- elementToElement( definition ) {
264
- // Set up downcast converter.
265
- this.for( 'downcast' ).elementToElement( definition );
266
-
267
- // Set up upcast converter.
268
- for ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {
269
- this.for( 'upcast' )
270
- .elementToElement( {
271
- model,
272
- view,
273
- converterPriority: definition.converterPriority
274
- } );
275
- }
276
- }
277
-
278
- /**
279
- * Sets up converters between the model and the view that convert a model attribute to a view element (and vice versa).
280
- * For example, a model text node with `"Foo"` as data and the `bold` attribute will be turned to `<strong>Foo</strong>` in the view.
281
- *
282
- * // A simple conversion from the `bold=true` attribute to the `<strong>` view element (and vice versa).
283
- * editor.conversion.attributeToElement( { model: 'bold', view: 'strong' } );
284
- *
285
- * // Override other converters by specifying a converter definition with a higher priority.
286
- * editor.conversion.attributeToElement( { model: 'bold', view: 'b', converterPriority: 'high' } );
287
- *
288
- * // View specified as an object instead of a string.
289
- * editor.conversion.attributeToElement( {
290
- * model: 'bold',
291
- * view: {
292
- * name: 'span',
293
- * classes: 'bold'
294
- * }
295
- * } );
296
- *
297
- * // Use `config.model.name` to define the conversion only from a given node type, `$text` in this case.
298
- * // The same attribute on different elements may then be handled by a different converter.
299
- * editor.conversion.attributeToElement( {
300
- * model: {
301
- * key: 'textDecoration',
302
- * values: [ 'underline', 'lineThrough' ],
303
- * name: '$text'
304
- * },
305
- * view: {
306
- * underline: {
307
- * name: 'span',
308
- * styles: {
309
- * 'text-decoration': 'underline'
310
- * }
311
- * },
312
- * lineThrough: {
313
- * name: 'span',
314
- * styles: {
315
- * 'text-decoration': 'line-through'
316
- * }
317
- * }
318
- * }
319
- * } );
320
- *
321
- * // Use `upcastAlso` to define other view elements that should also be converted to the `bold` attribute.
322
- * editor.conversion.attributeToElement( {
323
- * model: 'bold',
324
- * view: 'strong',
325
- * upcastAlso: [
326
- * 'b',
327
- * {
328
- * name: 'span',
329
- * classes: 'bold'
330
- * },
331
- * {
332
- * name: 'span',
333
- * styles: {
334
- * 'font-weight': 'bold'
335
- * }
336
- * },
337
- * viewElement => {
338
- * const fontWeight = viewElement.getStyle( 'font-weight' );
339
- *
340
- * if ( viewElement.is( 'element', 'span' ) && fontWeight && /\d+/.test() && Number( fontWeight ) > 500 ) {
341
- * // Returned value can be an object with the matched properties.
342
- * // These properties will be "consumed" during the conversion.
343
- * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
344
- *
345
- * return {
346
- * name: true,
347
- * styles: [ 'font-weight' ]
348
- * };
349
- * }
350
- * }
351
- * ]
352
- * } );
353
- *
354
- * // Conversion from and to a model attribute key whose value is an enum (`fontSize=big|small`).
355
- * // `upcastAlso` set as callback enables a conversion of a wide range of different view elements.
356
- * editor.conversion.attributeToElement( {
357
- * model: {
358
- * key: 'fontSize',
359
- * values: [ 'big', 'small' ]
360
- * },
361
- * view: {
362
- * big: {
363
- * name: 'span',
364
- * styles: {
365
- * 'font-size': '1.2em'
366
- * }
367
- * },
368
- * small: {
369
- * name: 'span',
370
- * styles: {
371
- * 'font-size': '0.8em'
372
- * }
373
- * }
374
- * },
375
- * upcastAlso: {
376
- * big: viewElement => {
377
- * const fontSize = viewElement.getStyle( 'font-size' );
378
- *
379
- * if ( !fontSize ) {
380
- * return null;
381
- * }
382
- *
383
- * const match = fontSize.match( /(\d+)\s*px/ );
384
- *
385
- * if ( !match ) {
386
- * return null;
387
- * }
388
- *
389
- * const size = Number( match[ 1 ] );
390
- *
391
- * if ( viewElement.is( 'element', 'span' ) && size > 10 ) {
392
- * // Returned value can be an object with the matched properties.
393
- * // These properties will be "consumed" during the conversion.
394
- * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
395
- *
396
- * return { name: true, styles: [ 'font-size' ] };
397
- * }
398
- *
399
- * return null;
400
- * },
401
- * small: viewElement => {
402
- * const fontSize = viewElement.getStyle( 'font-size' );
403
- *
404
- * if ( !fontSize ) {
405
- * return null;
406
- * }
407
- *
408
- * const match = fontSize.match( /(\d+)\s*px/ );
409
- *
410
- * if ( !match ) {
411
- * return null;
412
- * }
413
- *
414
- * const size = Number( match[ 1 ] );
415
- *
416
- * if ( viewElement.is( 'element', 'span' ) && size < 10 ) {
417
- * // Returned value can be an object with the matched properties.
418
- * // These properties will be "consumed" during the conversion.
419
- * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
420
- *
421
- * return { name: true, styles: [ 'font-size' ] };
422
- * }
423
- *
424
- * return null;
425
- * }
426
- * }
427
- * } );
428
- *
429
- * The `definition.model` parameter specifies which model attribute should be converted from or to. It can be a `{ key, value }` object
430
- * describing the attribute key and value to convert or a `String` specifying just the attribute key (in such a case
431
- * `value` is set to `true`).
432
- * See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.
433
- *
434
- * @param {module:engine/conversion/conversion~ConverterDefinition} definition The converter definition.
435
- */
436
- attributeToElement( definition ) {
437
- // Set up downcast converter.
438
- this.for( 'downcast' ).attributeToElement( definition );
439
-
440
- // Set up upcast converter.
441
- for ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {
442
- this.for( 'upcast' )
443
- .elementToAttribute( {
444
- view,
445
- model,
446
- converterPriority: definition.converterPriority
447
- } );
448
- }
449
- }
450
-
451
- /**
452
- * Sets up converters between the model and the view that convert a model attribute to a view attribute (and vice versa). For example,
453
- * `<imageBlock src='foo.jpg'></imageBlock>` is converted to `<img src='foo.jpg'></img>` (the same attribute key and value).
454
- * This type of converters is intended to be used with {@link module:engine/model/element~Element model element} nodes.
455
- * To convert the text attributes,
456
- * the {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement converter`}should be set up.
457
- *
458
- * // A simple conversion from the `source` model attribute to the `src` view attribute (and vice versa).
459
- * editor.conversion.attributeToAttribute( { model: 'source', view: 'src' } );
460
- *
461
- * // Attribute values are strictly specified.
462
- * editor.conversion.attributeToAttribute( {
463
- * model: {
464
- * name: 'imageInline',
465
- * key: 'aside',
466
- * values: [ 'aside' ]
467
- * },
468
- * view: {
469
- * aside: {
470
- * name: 'img',
471
- * key: 'class',
472
- * value: [ 'aside', 'half-size' ]
473
- * }
474
- * }
475
- * } );
476
- *
477
- * // Set the style attribute.
478
- * editor.conversion.attributeToAttribute( {
479
- * model: {
480
- * name: 'imageInline',
481
- * key: 'aside',
482
- * values: [ 'aside' ]
483
- * },
484
- * view: {
485
- * aside: {
486
- * name: 'img',
487
- * key: 'style',
488
- * value: {
489
- * float: 'right',
490
- * width: '50%',
491
- * margin: '5px'
492
- * }
493
- * }
494
- * }
495
- * } );
496
- *
497
- * // Conversion from and to a model attribute key whose value is an enum (`align=right|center`).
498
- * // Use `upcastAlso` to define other view elements that should also be converted to the `align=right` attribute.
499
- * editor.conversion.attributeToAttribute( {
500
- * model: {
501
- * key: 'align',
502
- * values: [ 'right', 'center' ]
503
- * },
504
- * view: {
505
- * right: {
506
- * key: 'class',
507
- * value: 'align-right'
508
- * },
509
- * center: {
510
- * key: 'class',
511
- * value: 'align-center'
512
- * }
513
- * },
514
- * upcastAlso: {
515
- * right: {
516
- * styles: {
517
- * 'text-align': 'right'
518
- * }
519
- * },
520
- * center: {
521
- * styles: {
522
- * 'text-align': 'center'
523
- * }
524
- * }
525
- * }
526
- * } );
527
- *
528
- * The `definition.model` parameter specifies which model attribute should be converted from and to.
529
- * It can be a `{ key, [ values ], [ name ] }` object or a `String`, which will be treated like `{ key: definition.model }`.
530
- * The `key` property is the model attribute key to convert from and to.
531
- * The `values` are the possible model attribute values. If the `values` parameter is not set, the model attribute value
532
- * will be the same as the view attribute value.
533
- * If `name` is set, the conversion will be set up only for model elements with the given name.
534
- *
535
- * The `definition.view` parameter specifies which view attribute should be converted from and to.
536
- * It can be a `{ key, value, [ name ] }` object or a `String`, which will be treated like `{ key: definition.view }`.
537
- * The `key` property is the view attribute key to convert from and to.
538
- * The `value` is the view attribute value to convert from and to. If `definition.value` is not set, the view attribute value will be
539
- * the same as the model attribute value.
540
- * If `key` is `'class'`, `value` can be a `String` or an array of `String`s.
541
- * If `key` is `'style'`, `value` is an object with key-value pairs.
542
- * In other cases, `value` is a `String`.
543
- * If `name` is set, the conversion will be set up only for model elements with the given name.
544
- * If `definition.model.values` is set, `definition.view` is an object that assigns values from `definition.model.values`
545
- * to `{ key, value, [ name ] }` objects.
546
- *
547
- * `definition.upcastAlso` specifies which other matching view elements should also be upcast to the given model configuration.
548
- * If `definition.model.values` is set, `definition.upcastAlso` should be an object assigning values from `definition.model.values`
549
- * to {@link module:engine/view/matcher~MatcherPattern}s or arrays of {@link module:engine/view/matcher~MatcherPattern}s.
550
- *
551
- * **Note:** `definition.model` and `definition.view` form should be mirrored, so the same types of parameters should
552
- * be given in both parameters.
553
- *
554
- * @param {Object} definition The converter definition.
555
- * @param {String|Object} definition.model The model attribute to convert from and to.
556
- * @param {String|Object} definition.view The view attribute to convert from and to.
557
- * @param {module:engine/view/matcher~MatcherPattern|Array.<module:engine/view/matcher~MatcherPattern>} [definition.upcastAlso]
558
- * Any view element matching `definition.upcastAlso` will also be converted to the given model attribute. `definition.upcastAlso`
559
- * is used only if `config.model.values` is specified.
560
- */
561
- attributeToAttribute( definition ) {
562
- // Set up downcast converter.
563
- this.for( 'downcast' ).attributeToAttribute( definition );
564
-
565
- // Set up upcast converter.
566
- for ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {
567
- this.for( 'upcast' )
568
- .attributeToAttribute( {
569
- view,
570
- model
571
- } );
572
- }
573
- }
574
-
575
- /**
576
- * Creates and caches conversion helpers for given dispatchers group.
577
- *
578
- * @private
579
- * @param {Object} options
580
- * @param {String} options.name Group name.
581
- * @param {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
582
- * module:engine/conversion/upcastdispatcher~UpcastDispatcher>} options.dispatchers
583
- * @param {Boolean} options.isDowncast
584
- */
585
- _createConversionHelpers( { name, dispatchers, isDowncast } ) {
586
- if ( this._helpers.has( name ) ) {
587
- /**
588
- * Trying to register a group name that has already been registered.
589
- *
590
- * @error conversion-group-exists
591
- */
592
- throw new CKEditorError( 'conversion-group-exists', this );
593
- }
594
-
595
- const helpers = isDowncast ? new DowncastHelpers( dispatchers ) : new UpcastHelpers( dispatchers );
596
-
597
- this._helpers.set( name, helpers );
598
- }
57
+ /**
58
+ * Creates a new conversion instance.
59
+ *
60
+ * @param {module:engine/conversion/downcastdispatcher~DowncastDispatcher|
61
+ * Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher>} downcastDispatchers
62
+ * @param {module:engine/conversion/upcastdispatcher~UpcastDispatcher|
63
+ * Array.<module:engine/conversion/upcastdispatcher~UpcastDispatcher>} upcastDispatchers
64
+ */
65
+ constructor(downcastDispatchers, upcastDispatchers) {
66
+ /**
67
+ * Maps dispatchers group name to ConversionHelpers instances.
68
+ *
69
+ * @private
70
+ * @member {Map.<String,module:engine/conversion/conversionhelpers~ConversionHelpers>}
71
+ */
72
+ this._helpers = new Map();
73
+ // Define default 'downcast' & 'upcast' dispatchers groups. Those groups are always available as two-way converters needs them.
74
+ this._downcast = toArray(downcastDispatchers);
75
+ this._createConversionHelpers({ name: 'downcast', dispatchers: this._downcast, isDowncast: true });
76
+ this._upcast = toArray(upcastDispatchers);
77
+ this._createConversionHelpers({ name: 'upcast', dispatchers: this._upcast, isDowncast: false });
78
+ }
79
+ /**
80
+ * Define an alias for registered dispatcher.
81
+ *
82
+ * const conversion = new Conversion(
83
+ * [ dataDowncastDispatcher, editingDowncastDispatcher ],
84
+ * upcastDispatcher
85
+ * );
86
+ *
87
+ * conversion.addAlias( 'dataDowncast', dataDowncastDispatcher );
88
+ *
89
+ * @param {String} alias An alias of a dispatcher.
90
+ * @param {module:engine/conversion/downcastdispatcher~DowncastDispatcher|
91
+ * module:engine/conversion/upcastdispatcher~UpcastDispatcher} dispatcher Dispatcher which should have an alias.
92
+ */
93
+ addAlias(alias, dispatcher) {
94
+ const isDowncast = this._downcast.includes(dispatcher);
95
+ const isUpcast = this._upcast.includes(dispatcher);
96
+ if (!isUpcast && !isDowncast) {
97
+ /**
98
+ * Trying to register an alias for a dispatcher that nas not been registered.
99
+ *
100
+ * @error conversion-add-alias-dispatcher-not-registered
101
+ */
102
+ throw new CKEditorError('conversion-add-alias-dispatcher-not-registered', this);
103
+ }
104
+ this._createConversionHelpers({ name: alias, dispatchers: [dispatcher], isDowncast });
105
+ }
106
+ /**
107
+ * Provides a chainable API to assign converters to a conversion dispatchers group.
108
+ *
109
+ * If the given group name has not been registered, the
110
+ * {@link module:utils/ckeditorerror~CKEditorError `conversion-for-unknown-group` error} is thrown.
111
+ *
112
+ * You can use conversion helpers available directly in the `for()` chain or your custom ones via
113
+ * the {@link module:engine/conversion/conversionhelpers~ConversionHelpers#add `add()`} method.
114
+ *
115
+ * # Using built-in conversion helpers
116
+ *
117
+ * The `for()` chain comes with a set of conversion helpers which you can use like this:
118
+ *
119
+ * editor.conversion.for( 'downcast' )
120
+ * .elementToElement( config1 ) // Adds an element-to-element downcast converter.
121
+ * .attributeToElement( config2 ); // Adds an attribute-to-element downcast converter.
122
+ *
123
+ * editor.conversion.for( 'upcast' )
124
+ * .elementToAttribute( config3 ); // Adds an element-to-attribute upcast converter.
125
+ *
126
+ * Refer to the documentation of built-in conversion helpers to learn about their configuration options.
127
+ *
128
+ * * downcast (model-to-view) conversion helpers:
129
+ *
130
+ * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`},
131
+ * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement `attributeToElement()`},
132
+ * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToAttribute `attributeToAttribute()`}.
133
+ * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToElement `markerToElement()`}.
134
+ * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToHighlight `markerToHighlight()`}.
135
+ *
136
+ * * upcast (view-to-model) conversion helpers:
137
+ *
138
+ * * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToElement `elementToElement()`},
139
+ * * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToAttribute `elementToAttribute()`},
140
+ * * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#attributeToAttribute `attributeToAttribute()`}.
141
+ * * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToMarker `elementToMarker()`}.
142
+ *
143
+ * # Using custom conversion helpers
144
+ *
145
+ * If you need to implement an atypical converter, you can do so by calling:
146
+ *
147
+ * editor.conversion.for( direction ).add( customHelper );
148
+ *
149
+ * The `.add()` method takes exactly one parameter, which is a function. This function should accept one parameter that
150
+ * is a dispatcher instance. The function should add an actual converter to the passed dispatcher instance.
151
+ *
152
+ * Example:
153
+ *
154
+ * editor.conversion.for( 'upcast' ).add( dispatcher => {
155
+ * dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
156
+ * // Do something with a view <a> element.
157
+ * } );
158
+ * } );
159
+ *
160
+ * Refer to the documentation of {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher}
161
+ * and {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} to learn how to write
162
+ * custom converters.
163
+ *
164
+ * @param {String} groupName The name of dispatchers group to add the converters to.
165
+ * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers|module:engine/conversion/upcasthelpers~UpcastHelpers}
166
+ */
167
+ for(groupName) {
168
+ if (!this._helpers.has(groupName)) {
169
+ /**
170
+ * Trying to add a converter to an unknown dispatchers group.
171
+ *
172
+ * @error conversion-for-unknown-group
173
+ */
174
+ throw new CKEditorError('conversion-for-unknown-group', this);
175
+ }
176
+ return this._helpers.get(groupName);
177
+ }
178
+ /**
179
+ * Sets up converters between the model and the view that convert a model element to a view element (and vice versa).
180
+ * For example, the model `<paragraph>Foo</paragraph>` is turned into `<p>Foo</p>` in the view.
181
+ *
182
+ * // A simple conversion from the `paragraph` model element to the `<p>` view element (and vice versa).
183
+ * editor.conversion.elementToElement( { model: 'paragraph', view: 'p' } );
184
+ *
185
+ * // Override other converters by specifying a converter definition with a higher priority.
186
+ * editor.conversion.elementToElement( { model: 'paragraph', view: 'div', converterPriority: 'high' } );
187
+ *
188
+ * // View specified as an object instead of a string.
189
+ * editor.conversion.elementToElement( {
190
+ * model: 'fancyParagraph',
191
+ * view: {
192
+ * name: 'p',
193
+ * classes: 'fancy'
194
+ * }
195
+ * } );
196
+ *
197
+ * // Use `upcastAlso` to define other view elements that should also be converted to a `paragraph` element.
198
+ * editor.conversion.elementToElement( {
199
+ * model: 'paragraph',
200
+ * view: 'p',
201
+ * upcastAlso: [
202
+ * 'div',
203
+ * {
204
+ * // Any element with the `display: block` style.
205
+ * styles: {
206
+ * display: 'block'
207
+ * }
208
+ * }
209
+ * ]
210
+ * } );
211
+ *
212
+ * // `upcastAlso` set as callback enables a conversion of a wide range of different view elements.
213
+ * editor.conversion.elementToElement( {
214
+ * model: 'heading',
215
+ * view: 'h2',
216
+ * // Convert "heading-like" paragraphs to headings.
217
+ * upcastAlso: viewElement => {
218
+ * const fontSize = viewElement.getStyle( 'font-size' );
219
+ *
220
+ * if ( !fontSize ) {
221
+ * return null;
222
+ * }
223
+ *
224
+ * const match = fontSize.match( /(\d+)\s*px/ );
225
+ *
226
+ * if ( !match ) {
227
+ * return null;
228
+ * }
229
+ *
230
+ * const size = Number( match[ 1 ] );
231
+ *
232
+ * if ( size > 26 ) {
233
+ * // Returned value can be an object with the matched properties.
234
+ * // These properties will be "consumed" during the conversion.
235
+ * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
236
+ *
237
+ * return { name: true, styles: [ 'font-size' ] };
238
+ * }
239
+ *
240
+ * return null;
241
+ * }
242
+ * } );
243
+ *
244
+ * `definition.model` is a `String` with a model element name to convert from or to.
245
+ * See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.
246
+ *
247
+ * @param {module:engine/conversion/conversion~ConverterDefinition} definition The converter definition.
248
+ */
249
+ elementToElement(definition) {
250
+ // Set up downcast converter.
251
+ this.for('downcast').elementToElement(definition);
252
+ // Set up upcast converter.
253
+ for (const { model, view } of _getAllUpcastDefinitions(definition)) {
254
+ this.for('upcast')
255
+ .elementToElement({
256
+ model,
257
+ view,
258
+ converterPriority: definition.converterPriority
259
+ });
260
+ }
261
+ }
262
+ /**
263
+ * Sets up converters between the model and the view that convert a model attribute to a view element (and vice versa).
264
+ * For example, a model text node with `"Foo"` as data and the `bold` attribute will be turned to `<strong>Foo</strong>` in the view.
265
+ *
266
+ * // A simple conversion from the `bold=true` attribute to the `<strong>` view element (and vice versa).
267
+ * editor.conversion.attributeToElement( { model: 'bold', view: 'strong' } );
268
+ *
269
+ * // Override other converters by specifying a converter definition with a higher priority.
270
+ * editor.conversion.attributeToElement( { model: 'bold', view: 'b', converterPriority: 'high' } );
271
+ *
272
+ * // View specified as an object instead of a string.
273
+ * editor.conversion.attributeToElement( {
274
+ * model: 'bold',
275
+ * view: {
276
+ * name: 'span',
277
+ * classes: 'bold'
278
+ * }
279
+ * } );
280
+ *
281
+ * // Use `config.model.name` to define the conversion only from a given node type, `$text` in this case.
282
+ * // The same attribute on different elements may then be handled by a different converter.
283
+ * editor.conversion.attributeToElement( {
284
+ * model: {
285
+ * key: 'textDecoration',
286
+ * values: [ 'underline', 'lineThrough' ],
287
+ * name: '$text'
288
+ * },
289
+ * view: {
290
+ * underline: {
291
+ * name: 'span',
292
+ * styles: {
293
+ * 'text-decoration': 'underline'
294
+ * }
295
+ * },
296
+ * lineThrough: {
297
+ * name: 'span',
298
+ * styles: {
299
+ * 'text-decoration': 'line-through'
300
+ * }
301
+ * }
302
+ * }
303
+ * } );
304
+ *
305
+ * // Use `upcastAlso` to define other view elements that should also be converted to the `bold` attribute.
306
+ * editor.conversion.attributeToElement( {
307
+ * model: 'bold',
308
+ * view: 'strong',
309
+ * upcastAlso: [
310
+ * 'b',
311
+ * {
312
+ * name: 'span',
313
+ * classes: 'bold'
314
+ * },
315
+ * {
316
+ * name: 'span',
317
+ * styles: {
318
+ * 'font-weight': 'bold'
319
+ * }
320
+ * },
321
+ * viewElement => {
322
+ * const fontWeight = viewElement.getStyle( 'font-weight' );
323
+ *
324
+ * if ( viewElement.is( 'element', 'span' ) && fontWeight && /\d+/.test() && Number( fontWeight ) > 500 ) {
325
+ * // Returned value can be an object with the matched properties.
326
+ * // These properties will be "consumed" during the conversion.
327
+ * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
328
+ *
329
+ * return {
330
+ * name: true,
331
+ * styles: [ 'font-weight' ]
332
+ * };
333
+ * }
334
+ * }
335
+ * ]
336
+ * } );
337
+ *
338
+ * // Conversion from and to a model attribute key whose value is an enum (`fontSize=big|small`).
339
+ * // `upcastAlso` set as callback enables a conversion of a wide range of different view elements.
340
+ * editor.conversion.attributeToElement( {
341
+ * model: {
342
+ * key: 'fontSize',
343
+ * values: [ 'big', 'small' ]
344
+ * },
345
+ * view: {
346
+ * big: {
347
+ * name: 'span',
348
+ * styles: {
349
+ * 'font-size': '1.2em'
350
+ * }
351
+ * },
352
+ * small: {
353
+ * name: 'span',
354
+ * styles: {
355
+ * 'font-size': '0.8em'
356
+ * }
357
+ * }
358
+ * },
359
+ * upcastAlso: {
360
+ * big: viewElement => {
361
+ * const fontSize = viewElement.getStyle( 'font-size' );
362
+ *
363
+ * if ( !fontSize ) {
364
+ * return null;
365
+ * }
366
+ *
367
+ * const match = fontSize.match( /(\d+)\s*px/ );
368
+ *
369
+ * if ( !match ) {
370
+ * return null;
371
+ * }
372
+ *
373
+ * const size = Number( match[ 1 ] );
374
+ *
375
+ * if ( viewElement.is( 'element', 'span' ) && size > 10 ) {
376
+ * // Returned value can be an object with the matched properties.
377
+ * // These properties will be "consumed" during the conversion.
378
+ * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
379
+ *
380
+ * return { name: true, styles: [ 'font-size' ] };
381
+ * }
382
+ *
383
+ * return null;
384
+ * },
385
+ * small: viewElement => {
386
+ * const fontSize = viewElement.getStyle( 'font-size' );
387
+ *
388
+ * if ( !fontSize ) {
389
+ * return null;
390
+ * }
391
+ *
392
+ * const match = fontSize.match( /(\d+)\s*px/ );
393
+ *
394
+ * if ( !match ) {
395
+ * return null;
396
+ * }
397
+ *
398
+ * const size = Number( match[ 1 ] );
399
+ *
400
+ * if ( viewElement.is( 'element', 'span' ) && size < 10 ) {
401
+ * // Returned value can be an object with the matched properties.
402
+ * // These properties will be "consumed" during the conversion.
403
+ * // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
404
+ *
405
+ * return { name: true, styles: [ 'font-size' ] };
406
+ * }
407
+ *
408
+ * return null;
409
+ * }
410
+ * }
411
+ * } );
412
+ *
413
+ * The `definition.model` parameter specifies which model attribute should be converted from or to. It can be a `{ key, value }` object
414
+ * describing the attribute key and value to convert or a `String` specifying just the attribute key (in such a case
415
+ * `value` is set to `true`).
416
+ * See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.
417
+ *
418
+ * @param {module:engine/conversion/conversion~ConverterDefinition} definition The converter definition.
419
+ */
420
+ attributeToElement(definition) {
421
+ // Set up downcast converter.
422
+ this.for('downcast').attributeToElement(definition);
423
+ // Set up upcast converter.
424
+ for (const { model, view } of _getAllUpcastDefinitions(definition)) {
425
+ this.for('upcast')
426
+ .elementToAttribute({
427
+ view,
428
+ model,
429
+ converterPriority: definition.converterPriority
430
+ });
431
+ }
432
+ }
433
+ /**
434
+ * Sets up converters between the model and the view that convert a model attribute to a view attribute (and vice versa). For example,
435
+ * `<imageBlock src='foo.jpg'></imageBlock>` is converted to `<img src='foo.jpg'></img>` (the same attribute key and value).
436
+ * This type of converters is intended to be used with {@link module:engine/model/element~Element model element} nodes.
437
+ * To convert the text attributes,
438
+ * the {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement converter`}should be set up.
439
+ *
440
+ * // A simple conversion from the `source` model attribute to the `src` view attribute (and vice versa).
441
+ * editor.conversion.attributeToAttribute( { model: 'source', view: 'src' } );
442
+ *
443
+ * // Attribute values are strictly specified.
444
+ * editor.conversion.attributeToAttribute( {
445
+ * model: {
446
+ * name: 'imageInline',
447
+ * key: 'aside',
448
+ * values: [ 'aside' ]
449
+ * },
450
+ * view: {
451
+ * aside: {
452
+ * name: 'img',
453
+ * key: 'class',
454
+ * value: [ 'aside', 'half-size' ]
455
+ * }
456
+ * }
457
+ * } );
458
+ *
459
+ * // Set the style attribute.
460
+ * editor.conversion.attributeToAttribute( {
461
+ * model: {
462
+ * name: 'imageInline',
463
+ * key: 'aside',
464
+ * values: [ 'aside' ]
465
+ * },
466
+ * view: {
467
+ * aside: {
468
+ * name: 'img',
469
+ * key: 'style',
470
+ * value: {
471
+ * float: 'right',
472
+ * width: '50%',
473
+ * margin: '5px'
474
+ * }
475
+ * }
476
+ * }
477
+ * } );
478
+ *
479
+ * // Conversion from and to a model attribute key whose value is an enum (`align=right|center`).
480
+ * // Use `upcastAlso` to define other view elements that should also be converted to the `align=right` attribute.
481
+ * editor.conversion.attributeToAttribute( {
482
+ * model: {
483
+ * key: 'align',
484
+ * values: [ 'right', 'center' ]
485
+ * },
486
+ * view: {
487
+ * right: {
488
+ * key: 'class',
489
+ * value: 'align-right'
490
+ * },
491
+ * center: {
492
+ * key: 'class',
493
+ * value: 'align-center'
494
+ * }
495
+ * },
496
+ * upcastAlso: {
497
+ * right: {
498
+ * styles: {
499
+ * 'text-align': 'right'
500
+ * }
501
+ * },
502
+ * center: {
503
+ * styles: {
504
+ * 'text-align': 'center'
505
+ * }
506
+ * }
507
+ * }
508
+ * } );
509
+ *
510
+ * The `definition.model` parameter specifies which model attribute should be converted from and to.
511
+ * It can be a `{ key, [ values ], [ name ] }` object or a `String`, which will be treated like `{ key: definition.model }`.
512
+ * The `key` property is the model attribute key to convert from and to.
513
+ * The `values` are the possible model attribute values. If the `values` parameter is not set, the model attribute value
514
+ * will be the same as the view attribute value.
515
+ * If `name` is set, the conversion will be set up only for model elements with the given name.
516
+ *
517
+ * The `definition.view` parameter specifies which view attribute should be converted from and to.
518
+ * It can be a `{ key, value, [ name ] }` object or a `String`, which will be treated like `{ key: definition.view }`.
519
+ * The `key` property is the view attribute key to convert from and to.
520
+ * The `value` is the view attribute value to convert from and to. If `definition.value` is not set, the view attribute value will be
521
+ * the same as the model attribute value.
522
+ * If `key` is `'class'`, `value` can be a `String` or an array of `String`s.
523
+ * If `key` is `'style'`, `value` is an object with key-value pairs.
524
+ * In other cases, `value` is a `String`.
525
+ * If `name` is set, the conversion will be set up only for model elements with the given name.
526
+ * If `definition.model.values` is set, `definition.view` is an object that assigns values from `definition.model.values`
527
+ * to `{ key, value, [ name ] }` objects.
528
+ *
529
+ * `definition.upcastAlso` specifies which other matching view elements should also be upcast to the given model configuration.
530
+ * If `definition.model.values` is set, `definition.upcastAlso` should be an object assigning values from `definition.model.values`
531
+ * to {@link module:engine/view/matcher~MatcherPattern}s or arrays of {@link module:engine/view/matcher~MatcherPattern}s.
532
+ *
533
+ * **Note:** `definition.model` and `definition.view` form should be mirrored, so the same types of parameters should
534
+ * be given in both parameters.
535
+ *
536
+ * @param {Object} definition The converter definition.
537
+ * @param {String|Object} definition.model The model attribute to convert from and to.
538
+ * @param {String|Object} definition.view The view attribute to convert from and to.
539
+ * @param {module:engine/view/matcher~MatcherPattern|Array.<module:engine/view/matcher~MatcherPattern>} [definition.upcastAlso]
540
+ * Any view element matching `definition.upcastAlso` will also be converted to the given model attribute. `definition.upcastAlso`
541
+ * is used only if `config.model.values` is specified.
542
+ */
543
+ attributeToAttribute(definition) {
544
+ // Set up downcast converter.
545
+ this.for('downcast').attributeToAttribute(definition);
546
+ // Set up upcast converter.
547
+ for (const { model, view } of _getAllUpcastDefinitions(definition)) {
548
+ this.for('upcast')
549
+ .attributeToAttribute({
550
+ view,
551
+ model
552
+ });
553
+ }
554
+ }
555
+ /**
556
+ * Creates and caches conversion helpers for given dispatchers group.
557
+ *
558
+ * @private
559
+ * @param {Object} options
560
+ * @param {String} options.name Group name.
561
+ * @param {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
562
+ * module:engine/conversion/upcastdispatcher~UpcastDispatcher>} options.dispatchers
563
+ * @param {Boolean} options.isDowncast
564
+ */
565
+ _createConversionHelpers({ name, dispatchers, isDowncast }) {
566
+ if (this._helpers.has(name)) {
567
+ /**
568
+ * Trying to register a group name that has already been registered.
569
+ *
570
+ * @error conversion-group-exists
571
+ */
572
+ throw new CKEditorError('conversion-group-exists', this);
573
+ }
574
+ const helpers = isDowncast ?
575
+ new DowncastHelpers(dispatchers) :
576
+ new UpcastHelpers(dispatchers);
577
+ this._helpers.set(name, helpers);
578
+ }
599
579
  }
600
-
601
580
  /**
602
581
  * Defines how the model should be converted from and to the view.
603
582
  *
@@ -614,32 +593,29 @@ export default class Conversion {
614
593
  * (`upcastAlso` object values).
615
594
  * @property {module:utils/priorities~PriorityString} [converterPriority] The converter priority.
616
595
  */
617
-
618
596
  // Helper function that creates a joint array out of an item passed in `definition.view` and items passed in
619
597
  // `definition.upcastAlso`.
620
598
  //
621
599
  // @param {module:engine/conversion/conversion~ConverterDefinition} definition
622
600
  // @returns {Array} Array containing view definitions.
623
- function* _getAllUpcastDefinitions( definition ) {
624
- if ( definition.model.values ) {
625
- for ( const value of definition.model.values ) {
626
- const model = { key: definition.model.key, value };
627
- const view = definition.view[ value ];
628
- const upcastAlso = definition.upcastAlso ? definition.upcastAlso[ value ] : undefined;
629
-
630
- yield* _getUpcastDefinition( model, view, upcastAlso );
631
- }
632
- } else {
633
- yield* _getUpcastDefinition( definition.model, definition.view, definition.upcastAlso );
634
- }
601
+ function* _getAllUpcastDefinitions(definition) {
602
+ if (definition.model.values) {
603
+ for (const value of definition.model.values) {
604
+ const model = { key: definition.model.key, value };
605
+ const view = definition.view[value];
606
+ const upcastAlso = definition.upcastAlso ? definition.upcastAlso[value] : undefined;
607
+ yield* _getUpcastDefinition(model, view, upcastAlso);
608
+ }
609
+ }
610
+ else {
611
+ yield* _getUpcastDefinition(definition.model, definition.view, definition.upcastAlso);
612
+ }
635
613
  }
636
-
637
- function* _getUpcastDefinition( model, view, upcastAlso ) {
638
- yield { model, view };
639
-
640
- if ( upcastAlso ) {
641
- for ( const upcastAlsoItem of toArray( upcastAlso ) ) {
642
- yield { model, view: upcastAlsoItem };
643
- }
644
- }
614
+ function* _getUpcastDefinition(model, view, upcastAlso) {
615
+ yield { model, view };
616
+ if (upcastAlso) {
617
+ for (const upcastAlsoItem of toArray(upcastAlso)) {
618
+ yield { model, view: upcastAlsoItem };
619
+ }
620
+ }
645
621
  }