瀏覽代碼

Added toMarker view -> model converter.

Oskar Wróbel 8 年之前
父節點
當前提交
dacb9b2e3a

+ 48 - 0
packages/ckeditor5-engine/src/conversion/buildviewconverter.js

@@ -11,6 +11,7 @@ import Matcher from '../view/matcher';
 import ModelElement from '../model/element';
 import ModelPosition from '../model/position';
 import modelWriter from '../model/writer';
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
 
 /**
@@ -361,6 +362,53 @@ class ViewConverterBuilder {
 		this._setCallback( eventCallbackGen, 'low' );
 	}
 
+	toMarker( creator ) {
+		const eventCallbackGen = function( from ) {
+			return ( evt, data, consumable ) => {
+				// There is one callback for all patterns in the matcher.
+				// This will be usually just one pattern but we support matchers with many patterns too.
+				const matchAll = from.matcher.matchAll( data.input );
+
+				// If there is no match, this callback should not do anything.
+				if ( !matchAll ) {
+					return;
+				}
+
+				let modelElement;
+
+				// When creator is provided then create model element basing on creator function.
+				if ( creator instanceof Function ) {
+					modelElement = creator( data.input );
+					// When there is no creator then create model element basing on data from view element.
+				} else {
+					modelElement = new ModelElement( '$marker', { 'marker-name': data.input.getAttribute( 'marker-name' ) } );
+				}
+
+				// Check if model element is correct (has proper name and `markerName` property is defined).
+				if ( modelElement.name != '$marker' || typeof modelElement.getAttribute( 'marker-name' ) != 'string' ) {
+					throw new CKEditorError(
+						'build-view-converter-invalid-marker: Invalid model element to mark marker range.'
+					);
+				}
+
+				// Now, for every match between matcher and actual element, we will try to consume the match.
+				for ( const match of matchAll ) {
+					// Try to consume appropriate values from consumable values list.
+					if ( !consumable.consume( data.input, from.consume || match.match ) ) {
+						continue;
+					}
+
+					data.output = modelElement;
+
+					// Prevent multiple conversion if there are other correct matches.
+					break;
+				}
+			};
+		};
+
+		this._setCallback( eventCallbackGen, 'normal' );
+	}
+
 	/**
 	 * Helper function that uses given callback generator to created callback function and sets it on registered dispatchers.
 	 *

+ 89 - 0
packages/ckeditor5-engine/tests/conversion/buildviewconverter.js

@@ -20,6 +20,8 @@ import ViewMatcher from '../../src/view/matcher';
 
 import ViewConversionDispatcher from '../../src/conversion/viewconversiondispatcher';
 
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+
 import { convertToModelFragment, convertText } from '../../src/conversion/view-to-model-converters';
 
 function modelAttributesToString( item ) {
@@ -188,6 +190,93 @@ describe( 'View converter builder', () => {
 		expect( modelToString( result ) ).to.equal( '<paragraph><$text bold="true">aaabbbcccddd</$text></paragraph>' );
 	} );
 
+	it( 'should convert from pattern to marker', () => {
+		buildViewConverter().for( dispatcher ).from( { attribute: { 'marker-name': 'search' } } ).toMarker();
+
+		const viewElement = new ViewAttributeElement( 'span', { 'marker-name': 'search' } );
+
+		const result = dispatcher.convert( viewElement, objWithContext );
+		modelRoot.appendChildren( result );
+
+		expect( modelToString( result ) ).to.equal( '<$marker marker-name="search"></$marker>' );
+	} );
+
+	it( 'should convert from element to marker using creator function', () => {
+		buildViewConverter().for( dispatcher ).fromElement( 'marker' ).toMarker( ( data ) => {
+			return new ModelElement( '$marker', { 'marker-name': data.getAttribute( 'class' ) } );
+		} );
+
+		const element = new ViewAttributeElement( 'marker', { class: 'search' } );
+
+		const result = dispatcher.convert( element, objWithContext );
+		modelRoot.appendChildren( result );
+
+		expect( modelToString( result ) ).to.equal( '<$marker marker-name="search"></$marker>' );
+	} );
+
+	it( 'should convert from multiple view entities to marker', () => {
+		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
+
+		buildViewConverter().for( dispatcher )
+			.fromElement( 'marker' )
+			.from( { attribute: { 'marker-name': 'search' } } )
+			.toMarker();
+
+		const viewElement = new ViewContainerElement( 'p', null, [
+			new ViewAttributeElement( 'marker', { 'marker-name': 'comment' } ),
+			new ViewAttributeElement( 'span', { 'marker-name': 'search' } )
+		] );
+
+		const result = dispatcher.convert( viewElement, objWithContext );
+		modelRoot.appendChildren( result );
+
+		expect( modelToString( result ) ).to.equal(
+			'<paragraph><$marker marker-name="comment"></$marker><$marker marker-name="search"></$marker></paragraph>'
+		);
+	} );
+
+	it( 'should do nothing when there is no element matching to marker pattern', () => {
+		buildViewConverter().for( dispatcher ).from( { class: 'color' } ).toMarker();
+
+		const element = new ViewAttributeElement( 'span' );
+
+		expect( dispatcher.convert( element, objWithContext ) ).to.null;
+	} );
+
+	it( 'should throw an error when view element in not valid to convert to marker', () => {
+		buildViewConverter().for( dispatcher ).fromElement( 'marker' ).toMarker();
+
+		const element = new ViewAttributeElement( 'marker', { class: 'search' } );
+
+		expect( () => {
+			dispatcher.convert( element, objWithContext );
+		} ).to.throw( CKEditorError, /^build-view-converter-invalid-marker/ );
+	} );
+
+	it( 'should throw an error when model element returned by creator has not valid name', () => {
+		buildViewConverter().for( dispatcher ).fromElement( 'marker' ).toMarker( () => {
+			return new ModelElement( 'element', { 'marker-name': 'search' } );
+		} );
+
+		const element = new ViewAttributeElement( 'marker', { 'marker-name': 'search' } );
+
+		expect( () => {
+			dispatcher.convert( element, objWithContext );
+		} ).to.throw( CKEditorError, /^build-view-converter-invalid-marker/ );
+	} );
+
+	it( 'should throw an error when model element returned by creator has not valid marker-name attribute', () => {
+		buildViewConverter().for( dispatcher ).fromElement( 'marker' ).toMarker( () => {
+			return new ModelElement( '$marker', { 'foo': 'search' } );
+		} );
+
+		const element = new ViewAttributeElement( 'marker', { 'marker-name': 'search' } );
+
+		expect( () => {
+			dispatcher.convert( element, objWithContext );
+		} ).to.throw( CKEditorError, /^build-view-converter-invalid-marker/ );
+	} );
+
 	it( 'should convert from pattern to model element', () => {
 		buildViewConverter().for( dispatcher ).from(
 			{ name: 'span', class: 'megatron', attribute: { head: 'megatron', body: 'megatron', legs: 'megatron' } }