浏览代码

Added: Introduced `conversion.Conversion`.

Szymon Cofalik 7 年之前
父节点
当前提交
f903e3b242

+ 147 - 0
packages/ckeditor5-engine/src/conversion/conversion.js

@@ -0,0 +1,147 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module engine/conversion/conversion
+ */
+
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+
+/**
+ * An utility class that helps organizing dispatchers and adding converters to them.
+ */
+export default class Conversion {
+	/**
+	 * Creates new Conversion instance.
+	 */
+	constructor() {
+		this._dispatchersGroups = new Map();
+	}
+
+	/**
+	 * Registers one or more converters under given group name. Then, group name can be used to assign a converter
+	 * to multiple dispatchers at once.
+	 *
+	 * If given group name is used for a second time,
+	 * {@link module:utils/ckeditorerror~CKEditorError conversion-register-group-exists} error is thrown.
+	 *
+	 * @param {String} groupName A name for dispatchers group.
+	 * @param {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher|
+	 * module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers Dispatchers to register
+	 * under given name.
+	 */
+	register( groupName, dispatchers ) {
+		if ( this._dispatchersGroups.has( groupName ) ) {
+			/**
+			 * Trying to register a group name that was already registered.
+			 *
+			 * @error conversion-register-group-exists
+			 */
+			throw new CKEditorError( 'conversion-register-group-exists: Trying to register a group name that was already registered.' );
+		}
+
+		this._dispatchersGroups.set( groupName, dispatchers );
+	}
+
+	/**
+	 * Provides chainable API to assign converters to dispatchers registered under given group name. Converters are added
+	 * by calling `.add()` method of an object returned by this function.
+	 *
+	 *		conversion.for( 'model' )
+	 *			.add( conversionHelperA )
+	 *			.add( conversionHelperB );
+	 *
+	 * In above example, `conversionHelperA` and `conversionHelperB` will be called for all dispatchers from `'model'` group.
+	 *
+	 * `.add()` takes exactly one parameter, which is a function. That function should accept one parameter, which
+	 * is a dispatcher instance. The function should add an actual converter to passed dispatcher instance.
+	 *
+	 * Conversion helpers for most common cases are already provided. They are flexible enough to cover most use cases.
+	 * See documentation to learn how they can be configured.
+	 *
+	 * For model to view conversion, these are:
+	 *
+	 * * {@link module:engine/conversion/model-to-view-helpers~elementToElement model element to view element conversion helper},
+	 * * {@link module:engine/conversion/model-to-view-helpers~attributeToElement model attribute to view element conversion helper},
+	 * * {@link module:engine/conversion/model-to-view-helpers~attributeToAttribute model attribute to view attribute conversion helper}.
+	 *
+	 * For view to model conversion, these are:
+	 *
+	 * * view element to model element conversion helper,
+	 * * view element to model attribute conversion helper,
+	 * * view attribute to model attribute conversion helper.
+	 *
+	 * An example of using model conversion helpers to convert `paragraph` model element to `p` view element (and back):
+	 *
+	 *		// Define conversion configuration - model element 'paragraph' should be converted to view element 'p'.
+	 *		const config = { model: 'paragraph', view: 'p' };
+	 *
+	 *		// Add converters to proper dispatchers using conversion helpers.
+	 *		conversion.for( 'model' ).add( modelElementToElement( config ) );
+	 *		conversion.for( 'view' ).add( viewElementToElement( config ) );
+	 *
+	 * An example of providing custom conversion helper that uses custom converter function:
+	 *
+	 *		conversion.for( 'model' ).add( dispatcher => {
+	 *			// Adding custom `myConverter` converter for 'paragraph' element insertion, with default priority ('normal').
+	 *			dispatcher.on( 'insert:paragraph', myConverter );
+	 *		} );
+	 *
+	 * @param {String} groupName Name of dispatchers group to add converters to.
+	 * @returns {Object} Object with `.add()` method, providing a way to add converters.
+	 */
+	for( groupName ) {
+		const dispatchers = this._getDispatchers( groupName );
+
+		return {
+			add( conversionHelper ) {
+				_addToDispatchers( dispatchers, conversionHelper );
+
+				return this;
+			}
+		};
+	}
+
+	/**
+	 * Returns dispatchers registered under given group name.
+	 *
+	 * If given group name has not been registered,
+	 * {@link module:utils/ckeditorerror~CKEditorError conversion-for-unknown-group} error is thrown.
+	 *
+	 * @private
+	 * @param {String} groupName
+	 * @returns {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher|
+	 * module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>}
+	 */
+	_getDispatchers( groupName ) {
+		const dispatchers = this._dispatchersGroups.get( groupName );
+
+		if ( !dispatchers ) {
+			/**
+			 * Trying to add a converter to an unknown dispatchers group.
+			 *
+			 * @error conversion-for-unknown-group
+			 */
+			throw new CKEditorError( 'conversion-for-unknown-group: Trying to add a converter to an unknown dispatchers group.' );
+		}
+
+		return dispatchers;
+	}
+}
+
+// Helper function for `Conversion` `.add()` method.
+//
+// Calls `conversionHelper` on each dispatcher from the group specified earlier in `.for()` call, effectively
+// adding converters to all specified dispatchers.
+//
+// @private
+// @param {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher|
+// module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers
+// @param {Function} conversionHelper
+function _addToDispatchers( dispatchers, conversionHelper ) {
+	for ( const dispatcher of dispatchers ) {
+		conversionHelper( dispatcher );
+	}
+}

+ 73 - 0
packages/ckeditor5-engine/tests/conversion/conversion.js

@@ -0,0 +1,73 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import Conversion from '../../src/conversion/conversion';
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+
+describe( 'Conversion', () => {
+	let conversion, dispA, dispB;
+
+	beforeEach( () => {
+		conversion = new Conversion();
+
+		// Placeholders. Will be used only to see if their were given as attribute for a spy function.
+		dispA = Symbol( 'dispA' );
+		dispB = Symbol( 'dispB' );
+
+		conversion.register( 'ab', [ dispA, dispB ] );
+		conversion.register( 'a', [ dispA ] );
+		conversion.register( 'b', [ dispB ] );
+	} );
+
+	describe( 'register()', () => {
+		it( 'should throw when trying to use same group name twice', () => {
+			expect( () => {
+				conversion.register( 'ab' );
+			} ).to.throw( CKEditorError, /conversion-register-group-exists/ );
+		} );
+	} );
+
+	describe( 'for()', () => {
+		it( 'should return object with .add() method', () => {
+			const forResult = conversion.for( 'ab' );
+
+			expect( forResult.add ).to.be.instanceof( Function );
+		} );
+
+		it( 'should throw if non-existing group name has been used', () => {
+			expect( () => {
+				conversion.for( 'foo' );
+			} ).to.throw( CKEditorError, /conversion-for-unknown-group/ );
+		} );
+	} );
+
+	describe( 'add()', () => {
+		let helperA, helperB;
+
+		beforeEach( () => {
+			helperA = sinon.stub();
+			helperB = sinon.stub();
+		} );
+
+		it( 'should be chainable', () => {
+			const forResult = conversion.for( 'ab' );
+			const addResult = forResult.add( () => {} );
+
+			expect( addResult ).to.equal( addResult.add( () => {} ) );
+		} );
+
+		it( 'should fire given helper for every dispatcher in given group', () => {
+			conversion.for( 'ab' ).add( helperA );
+
+			expect( helperA.calledWithExactly( dispA ) ).to.be.true;
+			expect( helperA.calledWithExactly( dispB ) ).to.be.true;
+
+			conversion.for( 'b' ).add( helperB );
+
+			expect( helperB.calledWithExactly( dispA ) ).to.be.false;
+			expect( helperB.calledWithExactly( dispB ) ).to.be.true;
+		} );
+	} );
+} );