99
1010import Matcher from '../view/matcher' ;
1111import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror' ;
12- import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable' ;
12+ import Position from '../model/position' ;
13+ import Range from '../model/range' ;
1314
1415/**
1516 * Provides chainable, high-level API to easily build basic view-to-model converters that are appended to given
@@ -269,12 +270,12 @@ class ViewConverterBuilder {
269270 */
270271 toElement ( element ) {
271272 function eventCallbackGen ( from ) {
272- return ( evt , data , consumable , conversionApi ) => {
273+ return ( evt , data , conversionApi ) => {
273274 const writer = conversionApi . writer ;
274275
275276 // There is one callback for all patterns in the matcher.
276277 // This will be usually just one pattern but we support matchers with many patterns too.
277- const matchAll = from . matcher . matchAll ( data . input ) ;
278+ const matchAll = from . matcher . matchAll ( data . viewItem ) ;
278279
279280 // If there is no match, this callback should not do anything.
280281 if ( ! matchAll ) {
@@ -284,38 +285,60 @@ class ViewConverterBuilder {
284285 // Now, for every match between matcher and actual element, we will try to consume the match.
285286 for ( const match of matchAll ) {
286287 // Create model element basing on creator function or element name.
287- const modelElement = element instanceof Function ? element ( data . input , writer ) : writer . createElement ( element ) ;
288+ const modelElement = element instanceof Function ? element ( data . viewItem , writer ) : writer . createElement ( element ) ;
288289
289290 // Do not convert if element building function returned falsy value.
290291 if ( ! modelElement ) {
291292 continue ;
292293 }
293294
294- if ( ! conversionApi . schema . checkChild ( data . context , modelElement ) ) {
295+ // When element was already consumed then skip it.
296+ if ( ! conversionApi . consumable . test ( data . viewItem , from . consume || match . match ) ) {
295297 continue ;
296298 }
297299
298- // Try to consume appropriate values from consumable values list.
299- if ( ! consumable . consume ( data . input , from . consume || match . match ) ) {
300+ // Find allowed parent for element that we are going to insert.
301+ // If current parent does not allow to insert element but one of the ancestors does
302+ // then split nodes to allowed parent.
303+ const splitResult = conversionApi . splitToAllowedParent ( modelElement , data . cursorPosition ) ;
304+
305+ // When there is no split result it means that we can't insert element to model tree, so let's skip it.
306+ if ( ! splitResult ) {
300307 continue ;
301308 }
302309
303- // If everything is fine, we are ready to start the conversion.
304- // Add newly created `modelElement` to the parents stack.
305- data . context . push ( modelElement ) ;
310+ // Insert element on allowed position.
311+ conversionApi . writer . insert ( modelElement , splitResult . position ) ;
306312
307- // Convert children of converted view element and append them to `modelElement` .
308- const modelChildren = conversionApi . convertChildren ( data . input , consumable , data ) ;
313+ // Convert children and insert to element .
314+ const childrenResult = conversionApi . convertChildren ( data . viewItem , Position . createAt ( modelElement ) ) ;
309315
310- for ( const child of Array . from ( modelChildren ) ) {
311- writer . append ( child , modelElement ) ;
312- }
316+ // Consume appropriate value from consumable values list.
317+ conversionApi . consumable . consume ( data . viewItem , from . consume || match . match ) ;
318+
319+ // Set conversion result range.
320+ data . modelRange = new Range (
321+ // Range should start before inserted element
322+ Position . createBefore ( modelElement ) ,
323+ // Should end after but we need to take into consideration that children could split our
324+ // element, so we need to move range after parent of the last converted child.
325+ // before: <allowed>[]</allowed>
326+ // after: <allowed>[<converted><child></child></converted><child></child><converted>]</converted></allowed>
327+ Position . createAfter ( childrenResult . cursorPosition . parent )
328+ ) ;
313329
314- // Remove created `modelElement` from the parents stack.
315- data . context . pop ( ) ;
330+ // Now we need to check where the cursorPosition should be.
331+ // If we had to split parent to insert our element then we want to continue conversion inside split parent.
332+ //
333+ // before: <allowed><notAllowed>[]</notAllowed></allowed>
334+ // after: <allowed><notAllowed></notAllowed><converted></converted><notAllowed>[]</notAllowed></allowed>
335+ if ( splitResult . cursorParent ) {
336+ data . cursorPosition = Position . createAt ( splitResult . cursorParent ) ;
316337
317- // Add `modelElement` as a result.
318- data . output = modelElement ;
338+ // Otherwise just continue after inserted element.
339+ } else {
340+ data . cursorPosition = data . modelRange . end ;
341+ }
319342
320343 // Prevent multiple conversion if there are other correct matches.
321344 break ;
@@ -345,10 +368,10 @@ class ViewConverterBuilder {
345368 */
346369 toAttribute ( keyOrCreator , value ) {
347370 function eventCallbackGen ( from ) {
348- return ( evt , data , consumable , conversionApi ) => {
371+ return ( evt , data , conversionApi ) => {
349372 // There is one callback for all patterns in the matcher.
350373 // This will be usually just one pattern but we support matchers with many patterns too.
351- const matchAll = from . matcher . matchAll ( data . input ) ;
374+ const matchAll = from . matcher . matchAll ( data . viewItem ) ;
352375
353376 // If there is no match, this callback should not do anything.
354377 if ( ! matchAll ) {
@@ -358,34 +381,39 @@ class ViewConverterBuilder {
358381 // Now, for every match between matcher and actual element, we will try to consume the match.
359382 for ( const match of matchAll ) {
360383 // Try to consume appropriate values from consumable values list.
361- if ( ! consumable . consume ( data . input , from . consume || match . match ) ) {
384+ if ( ! conversionApi . consumable . consume ( data . viewItem , from . consume || match . match ) ) {
362385 continue ;
363386 }
364387
365- // Since we are converting to attribute we need an output on which we will set the attribute.
366- // If the output is not created yet, we will create it.
367- if ( ! data . output ) {
368- data . output = conversionApi . convertChildren ( data . input , consumable , data ) ;
388+ // Since we are converting to attribute we need an range on which we will set the attribute.
389+ // If the range is not created yet, we will create it.
390+ if ( ! data . modelRange ) {
391+ // Convert children and set conversion result as a current data.
392+ data = Object . assign ( data , conversionApi . convertChildren ( data . viewItem , data . cursorPosition ) ) ;
369393 }
370394
371395 // Use attribute creator function, if provided.
372396 let attribute ;
373397
374398 if ( keyOrCreator instanceof Function ) {
375- attribute = keyOrCreator ( data . input ) ;
399+ attribute = keyOrCreator ( data . viewItem ) ;
376400
377401 if ( ! attribute ) {
378402 return ;
379403 }
380404 } else {
381405 attribute = {
382406 key : keyOrCreator ,
383- value : value ? value : data . input . getAttribute ( from . attributeKey )
407+ value : value ? value : data . viewItem . getAttribute ( from . attributeKey )
384408 } ;
385409 }
386410
387- // Set attribute on current `output`. `Schema` is checked inside this helper function.
388- setAttributeOn ( data . output , attribute , data , conversionApi ) ;
411+ // Set attribute on each item in range according to Schema.
412+ for ( const node of Array . from ( data . modelRange . getItems ( ) ) ) {
413+ if ( conversionApi . schema . checkAttribute ( node , attribute . key ) ) {
414+ conversionApi . writer . setAttribute ( attribute . key , attribute . value , node ) ;
415+ }
416+ }
389417
390418 // Prevent multiple conversion if there are other correct matches.
391419 break ;
@@ -431,12 +459,12 @@ class ViewConverterBuilder {
431459 */
432460 toMarker ( creator ) {
433461 function eventCallbackGen ( from ) {
434- return ( evt , data , consumable , conversionApi ) => {
462+ return ( evt , data , conversionApi ) => {
435463 const writer = conversionApi . writer ;
436464
437465 // There is one callback for all patterns in the matcher.
438466 // This will be usually just one pattern but we support matchers with many patterns too.
439- const matchAll = from . matcher . matchAll ( data . input ) ;
467+ const matchAll = from . matcher . matchAll ( data . viewItem ) ;
440468
441469 // If there is no match, this callback should not do anything.
442470 if ( ! matchAll ) {
@@ -447,10 +475,10 @@ class ViewConverterBuilder {
447475
448476 // When creator is provided then create model element basing on creator function.
449477 if ( creator instanceof Function ) {
450- modelElement = creator ( data . input ) ;
478+ modelElement = creator ( data . viewItem ) ;
451479 // When there is no creator then create model element basing on data from view element.
452480 } else {
453- modelElement = writer . createElement ( '$marker' , { 'data-name' : data . input . getAttribute ( 'data-name' ) } ) ;
481+ modelElement = writer . createElement ( '$marker' , { 'data-name' : data . viewItem . getAttribute ( 'data-name' ) } ) ;
454482 }
455483
456484 // Check if model element is correct (has proper name and property).
@@ -463,11 +491,19 @@ class ViewConverterBuilder {
463491 // Now, for every match between matcher and actual element, we will try to consume the match.
464492 for ( const match of matchAll ) {
465493 // Try to consume appropriate values from consumable values list.
466- if ( ! consumable . consume ( data . input , from . consume || match . match ) ) {
494+ if ( ! conversionApi . consumable . consume ( data . viewItem , from . consume || match . match ) ) {
467495 continue ;
468496 }
469497
470- data . output = modelElement ;
498+ // Tmp fix because multiple matchers are not properly matched and consumed.
499+ // See https://github.com/ckeditor/ckeditor5-engine/issues/1257.
500+ if ( data . modelRange ) {
501+ continue ;
502+ }
503+
504+ writer . insert ( modelElement , data . cursorPosition ) ;
505+ data . modelRange = Range . createOn ( modelElement ) ;
506+ data . cursorPosition = data . modelRange . end ;
471507
472508 // Prevent multiple conversion if there are other correct matches.
473509 break ;
@@ -504,22 +540,6 @@ class ViewConverterBuilder {
504540 }
505541}
506542
507- // Helper function that sets given attributes on given `module:engine/model/node~Node` or
508- // `module:engine/model/documentfragment~DocumentFragment`.
509- function setAttributeOn ( toChange , attribute , data , conversionApi ) {
510- if ( isIterable ( toChange ) ) {
511- for ( const node of toChange ) {
512- setAttributeOn ( node , attribute , data , conversionApi ) ;
513- }
514-
515- return ;
516- }
517-
518- if ( conversionApi . schema . checkAttribute ( toChange , attribute . key ) ) {
519- conversionApi . writer . setAttribute ( attribute . key , attribute . value , toChange ) ;
520- }
521- }
522-
523543/**
524544 * Entry point for view-to-model converters builder. This chainable API makes it easy to create basic, most common
525545 * view-to-model converters and attach them to provided dispatchers. The method returns an instance of
0 commit comments