Browse Source

Changed: Removed converter builders and definition based conversion.

Szymon Cofalik 7 years ago
parent
commit
da3f2fe4ca

+ 0 - 420
packages/ckeditor5-engine/src/conversion/buildmodelconverter.js

@@ -1,420 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/**
- * @module engine/conversion/buildmodelconverter
- */
-
-import {
-	insertElement,
-	insertUIElement,
-	removeUIElement,
-	changeAttribute,
-	wrap,
-	highlightText,
-	highlightElement,
-	removeHighlight
-} from './model-to-view-converters';
-
-import ViewAttributeElement from '../view/attributeelement';
-import ViewContainerElement from '../view/containerelement';
-import ViewUIElement from '../view/uielement';
-
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
-
-/**
- * Provides chainable, high-level API to easily build basic model-to-view converters that are appended to given
- * dispatchers. In many cases, this is the API that should be used to specify how abstract model elements and
- * attributes should be represented in the view (and then later in DOM). Instances of this class are created by
- * {@link module:engine/conversion/buildmodelconverter~buildModelConverter}.
- *
- * If you need more complex converters, see {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher},
- * {@link module:engine/conversion/model-to-view-converters}, {@link module:engine/conversion/modelconsumable~ModelConsumable},
- * {@link module:engine/conversion/mapper~Mapper}.
- *
- * Using this API it is possible to create five kinds of converters:
- *
- * 1. Model element to view element converter. This is a converter that takes the model element and represents it
- * in the view.
- *
- *		buildModelConverter().for( dispatcher ).fromElement( 'paragraph' ).toElement( 'p' );
- *		buildModelConverter().for( dispatcher ).fromElement( 'image' ).toElement( 'img' );
- *
- * 2. Model attribute to view attribute converter. This is a converter that operates on model element attributes
- * and converts them to view element attributes. It is suitable for elements like `image` (`src`, `title` attributes).
- *
- *		buildModelConverter().for( dispatcher ).fromElement( 'image' ).toElement( 'img' );
- *		buildModelConverter().for( dispatcher ).fromAttribute( 'src' ).toAttribute();
- *
- * 3. Model attribute to view element converter. This is a converter that takes model attributes and represents them
- * as view elements. Elements created by this kind of converter are wrapping other view elements. Wrapped view nodes
- * correspond to model nodes had converter attribute. It is suitable for attributes like `bold`, where `bold` attribute
- * set on model text nodes is converter to `strong` view element.
- *
- *		buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).toElement( 'strong' );
- *
- * 4. Model marker to view highlight converter. This is a converter that converts model markers to view highlight
- * described by {@link module:engine/conversion/model-to-view-converters~HighlightDescriptor} object passed to
- * {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder#toHighlight} method.
- *
- *		buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toHighlight( {
- *			class: 'search',
- *			priority: 20
- *		} );
- *
- * 5. Model marker to element converter. This is a converter that takes model marker and creates separate elements at
- * the beginning and at the end of the marker's range. For more information see
- * {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder#toElement} method.
- *
- *		buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toElement( 'span' );
- *
- * It is possible to provide various different parameters for
- * {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder#toElement},
- * {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder#toAttribute} and
- * {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder#toHighlight} methods.
- * See their descriptions to learn more.
- *
- * It is also possible to {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder#withPriority change default priority}
- * of created converters to decide which converter should be fired earlier and which later. This is useful if you have
- * a general converter but also want to provide different special-case converters (i.e. given model element is converted
- * always to given view element, but if it has given attribute it is converter to other view element). For this,
- * use {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder#withPriority withPriority} right after `from...` method.
- *
- * Note that `to...` methods are "terminators", which means that should be the last one used in building converter.
- *
- * You can use {@link module:engine/conversion/buildviewconverter~ViewConverterBuilder}
- * to create "opposite" converters - from view to model.
- */
-class ModelConverterBuilder {
-	/**
-	 * Creates `ModelConverterBuilder` with given `dispatchers` registered to it.
-	 */
-	constructor() {
-		/**
-		 * Dispatchers to which converters will be attached.
-		 *
-		 * @type {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher>}
-		 * @private
-		 */
-		this._dispatchers = [];
-
-		/**
-		 * Contains data about registered "from" query.
-		 *
-		 * @type {Object}
-		 * @private
-		 */
-		this._from = null;
-	}
-
-	/**
-	 * Set one or more dispatchers which the built converter will be attached to.
-	 *
-	 * @chainable
-	 * @param {...module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher} dispatchers One or more dispatchers.
-	 * @returns {module:engine/conversion/buildmodelconverter~ModelConverterBuilder}
-	 */
-	for( ...dispatchers ) {
-		this._dispatchers = dispatchers;
-
-		return this;
-	}
-
-	/**
-	 * Registers what model element should be converted.
-	 *
-	 * @chainable
-	 * @param {String} elementName Name of element to convert.
-	 * @returns {module:engine/conversion/buildmodelconverter~ModelConverterBuilder}
-	 */
-	fromElement( elementName ) {
-		this._from = {
-			type: 'element',
-			name: elementName,
-			priority: null
-		};
-
-		return this;
-	}
-
-	/**
-	 * Registers what model attribute should be converted.
-	 *
-	 * @chainable
-	 * @param {String} key Key of attribute to convert.
-	 * @returns {module:engine/conversion/buildmodelconverter~ModelConverterBuilder}
-	 */
-	fromAttribute( key ) {
-		this._from = {
-			type: 'attribute',
-			key,
-			priority: null
-		};
-
-		return this;
-	}
-
-	/**
-	 * Registers what type of marker should be converted.
-	 *
-	 * @chainable
-	 * @param {String} markerName Name of marker to convert.
-	 * @returns {module:engine/conversion/buildmodelconverter~ModelConverterBuilder}
-	 */
-	fromMarker( markerName ) {
-		this._from = {
-			type: 'marker',
-			name: markerName,
-			priority: null
-		};
-
-		return this;
-	}
-
-	/**
-	 * Changes default priority for built converter. The lower the number, the earlier converter will be fired.
-	 * Default priority is `10`.
-	 *
-	 * **Note:** Keep in mind that event priority, that is set by this modifier, is used for attribute priority
-	 * when {@link module:engine/view/writer~writer} is used. This changes how view elements are ordered,
-	 * i.e.: `<strong><em>foo</em></strong>` vs `<em><strong>foo</strong></em>`. Using priority you can also
-	 * prevent node merging, i.e.: `<span class="bold"><span class="theme">foo</span><span>` vs `<span class="bold theme">foo</span>`.
-	 * If you want to prevent merging, just set different priority for both converters.
-	 *
-	 *		buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).withPriority( 2 ).toElement( 'strong' );
-	 *		buildModelConverter().for( dispatcher ).fromAttribute( 'italic' ).withPriority( 3 ).toElement( 'em' );
-	 *
-	 * @chainable
-	 * @param {Number} priority Converter priority.
-	 * @returns {module:engine/conversion/buildmodelconverter~ModelConverterBuilder}
-	 */
-	withPriority( priority ) {
-		this._from.priority = priority;
-
-		return this;
-	}
-
-	/**
-	 * Registers what view element will be created by converter.
-	 *
-	 * Method accepts various ways of providing how the view element will be created. You can pass view element name as
-	 * `string`, view element instance which will be cloned and used, or creator function which returns view element that
-	 * will be used. Keep in mind that when you view element instance or creator function, it has to be/return a
-	 * proper type of view element: {@link module:engine/view/containerelement~ContainerElement ViewContainerElement} if you convert
-	 * from element, {@link module:engine/view/attributeelement~AttributeElement ViewAttributeElement} if you convert
-	 * from attribute and {@link module:engine/view/uielement~UIElement ViewUIElement} if you convert from marker.
-	 *
-	 * **Note:** When converting from model's marker, separate elements will be created at the beginning and at the end of the
-	 * marker's range. If range is collapsed then only one element will be created. See how markers
-	 * {module:engine/model/buildviewconverter~ViewConverterBuilder#toMarker serialization from view to model}
-	 * works to find out what view element format is the best for you.
-	 *
-	 *		buildModelConverter().for( dispatcher ).fromElement( 'paragraph' ).toElement( 'p' );
-	 *
-	 *		buildModelConverter().for( dispatcher ).fromElement( 'image' ).toElement( new ViewContainerElement( 'img' ) );
-	 *
-	 *		buildModelConverter().for( dispatcher )
-	 *			.fromElement( 'header' )
-	 *			.toElement( ( data ) => new ViewContainerElement( 'h' + data.item.getAttribute( 'level' ) ) );
-	 *
-	 *		buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).toElement( new ViewAttributeElement( 'strong' ) );
-	 *
-	 *		buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toElement( 'span' );
-	 *
-	 *		buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toElement( new ViewUIElement( 'span' ) );
-	 *
-	 * Creator function will be passed different values depending whether conversion is from element or from attribute:
-	 *
-	 * * from element: dispatcher's
-	 * {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#event:insert insert event}
-	 * parameters will be passed,
-	 * * from attribute: there is one parameter and it is attribute value,
-	 * * from marker: {@link module:engine/conversion/buildmodelconverter~MarkerViewElementCreatorData}.
-	 *
-	 * This method also registers model selection to view selection converter, if conversion is from attribute.
-	 *
-	 * This method creates the converter and adds it as a callback to a proper
-	 * {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher conversion dispatcher} event.
-	 *
-	 * @param {String|module:engine/view/element~Element|Function} element Element created by converter or
-	 * a function that returns view element.
-	 */
-	toElement( element ) {
-		const priority = this._from.priority === null ? 'normal' : this._from.priority;
-
-		for ( const dispatcher of this._dispatchers ) {
-			if ( this._from.type == 'element' ) {
-				// From model element to view element -> insert element.
-				element = typeof element == 'string' ? new ViewContainerElement( element ) : element;
-
-				dispatcher.on( 'insert:' + this._from.name, insertElement( element ), { priority } );
-			} else if ( this._from.type == 'attribute' ) {
-				// From model attribute to view element -> wrap.
-				element = typeof element == 'string' ? new ViewAttributeElement( element ) : element;
-
-				dispatcher.on( 'attribute:' + this._from.key, wrap( element ), { priority } );
-			} else {
-				// From marker to element.
-				const priority = this._from.priority === null ? 'normal' : this._from.priority;
-
-				element = typeof element == 'string' ? new ViewUIElement( element ) : element;
-
-				dispatcher.on( 'addMarker:' + this._from.name, insertUIElement( element ), { priority } );
-				dispatcher.on( 'removeMarker:' + this._from.name, removeUIElement( element ), { priority } );
-			}
-		}
-	}
-
-	/**
-	 * Registers that marker should be converted to view highlight. Markers, basically,
-	 * are {@link module:engine/model/liverange~LiveRange} instances, that are named. View highlight is
-	 * a representation of the model marker in the view:
-	 * * each {@link module:engine/view/text~Text view text node} in the marker's range will be wrapped with `span`
-	 * {@link module:engine/view/attributeelement~AttributeElement},
-	 * * each {@link module:engine/view/containerelement~ContainerElement container view element} in the marker's
-	 * range can handle highlighting individually by providing `addHighlight` and `removeHighlight`
-	 * custom properties:
-	 *
-	 *		viewElement.setCustomProperty( 'addHighlight', ( element, descriptor ) => {} );
-	 *		viewElement.setCustomProperty( 'removeHighlight', ( element, descriptorId ) => {} );
-	 *
-	 * {@link module:engine/conversion/model-to-view-converters~HighlightDescriptor} will be used to create
-	 * spans over text nodes and also will be provided to `addHighlight` and `removeHighlight` methods
-	 * each time highlight should be set or removed from view elements.
-	 *
-	 * **Note:** When `addHighlight` and `removeHighlight` custom properties are present, converter assumes
-	 * that element itself is taking care of presenting highlight on its child nodes, so it won't convert them.
-	 *
-	 * Highlight descriptor can be provided as plain object:
-	 *
-	 *		buildModelConverter.for( dispatcher ).fromMarker( 'search' ).toHighlight( { class: 'search-highlight' } );
- 	 *
-	 * Also, descriptor creator function can be provided:
-	 *
-	 *		buildModelConverter.for( dispatcher ).fromMarker( 'search:blue' ).toHighlight( data => {
-	 *			const color = data.markerName.split( ':' )[ 1 ];
-	 *
-	 *			return { class: 'search-' + color };
-	 *		} );
-	 *
-	 * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
-	 * `build-model-converter-non-marker-to-highlight` when trying to convert not from marker.
-	 *
-	 * @param {function|module:engine/conversion/model-to-view-converters~HighlightDescriptor} highlightDescriptor
-	 */
-	toHighlight( highlightDescriptor ) {
-		const priority = this._from.priority === null ? 'normal' : this._from.priority;
-
-		if ( this._from.type != 'marker' ) {
-			/**
-			 * Conversion to a highlight is supported only from model markers.
-			 *
-			 * @error build-model-converter-non-marker-to-highlight
-			 */
-			throw new CKEditorError(
-				'build-model-converter-non-marker-to-highlight: Conversion to a highlight is supported ' +
-				'only from model markers.'
-			);
-		}
-
-		for ( const dispatcher of this._dispatchers ) {
-			dispatcher.on( 'addMarker:' + this._from.name, highlightText( highlightDescriptor ), { priority } );
-			dispatcher.on( 'addMarker:' + this._from.name, highlightElement( highlightDescriptor ), { priority } );
-
-			dispatcher.on( 'removeMarker:' + this._from.name, removeHighlight( highlightDescriptor ), { priority } );
-		}
-	}
-
-	/**
-	 * Registers what view attribute will be created by converter. Keep in mind, that only model attribute to
-	 * view attribute conversion is supported.
-	 *
-	 * Method accepts various ways of providing how the view attribute will be created:
-	 *
-	 * * for no passed parameter, attribute key and value will be converted 1-to-1 to view attribute,
-	 * * if you pass one `string`, it will be used as new attribute key while attribute value will be copied,
-	 * * if you pass two `string`s, first one will be used as new attribute key and second one as new attribute value,
-	 * * if you pass a function, it is expected to return an object with `key` and `value` properties representing attribute key and value.
-	 * This function will be passed model attribute value and model attribute key as first two parameters and then
-	 * all dispatcher's
-	 * {module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#event:changeAttribute changeAttribute event}
-	 * parameters.
-	 *
-	 *		buildModelConverter().for( dispatcher ).fromAttribute( 'class' ).toAttribute( '' );
-	 *
-	 *		buildModelConverter().for( dispatcher ).fromAttribute( 'linkTitle' ).toAttribute( 'title' );
-	 *
-	 *		buildModelConverter().for( dispatcher ).fromAttribute( 'highlighted' ).toAttribute( 'style', 'background:yellow' );
-	 *
-	 *		buildModelConverter().for( dispatcher )
-	 *			.fromAttribute( 'theme' )
-	 *			.toAttribute( ( value ) => ( { key: 'class', value: value + '-theme' } ) );
-	 *
-	 * This method creates the converter and adds it as a callback to a proper
-	 * {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher conversion dispatcher} event.
-	 *
-	 * @param {String|Function} [keyOrCreator] Attribute key or a creator function.
-	 * @param {*} [value] Attribute value.
-	 */
-	toAttribute( keyOrCreator, value ) {
-		if ( this._from.type != 'attribute' ) {
-			/**
-			 * To-attribute conversion is supported only for model attributes.
-			 *
-			 * @error build-model-converter-element-to-attribute
-			 */
-			throw new CKEditorError( 'build-model-converter-non-attribute-to-attribute: ' +
-				'To-attribute conversion is supported only from model attributes.' );
-		}
-
-		let attributeCreator;
-
-		if ( !keyOrCreator ) {
-			// If `keyOrCreator` is not set, we assume default behavior which is 1:1 attribute re-write.
-			// This is also a default behavior for `changeAttribute` converter when no attribute creator is passed.
-			attributeCreator = undefined;
-		} else if ( typeof keyOrCreator == 'string' ) {
-			// `keyOrCreator` is an attribute key.
-
-			if ( value ) {
-				// If value is set, create "dumb" creator that always returns the same object.
-				attributeCreator = function() {
-					return { key: keyOrCreator, value };
-				};
-			} else {
-				// If value is not set, take it from the passed parameter.
-				attributeCreator = function( value ) {
-					return { key: keyOrCreator, value };
-				};
-			}
-		} else {
-			// `keyOrCreator` is an attribute creator function.
-			attributeCreator = keyOrCreator;
-		}
-
-		for ( const dispatcher of this._dispatchers ) {
-			const options = { priority: this._from.priority || 'normal' };
-
-			dispatcher.on( 'attribute:' + this._from.key, changeAttribute( attributeCreator ), options );
-		}
-	}
-}
-
-/**
- * Entry point for model-to-view converters builder. This chainable API makes it easy to create basic, most common
- * model-to-view converters and attach them to provided dispatchers. The method returns an instance of
- * {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder}.
- */
-export default function buildModelConverter() {
-	return new ModelConverterBuilder();
-}
-
-/**
- * @typedef {Object} module:engine/conversion/buildmodelconverter~MarkerViewElementCreatorData
- *
- * @param {String} markerName Marker name.
- * @param {module:engine/model/range~Range} markerRange Marker range.
- * @param {Boolean} isOpening Defines if currently converted element is a beginning or end of the marker range.
- */

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

@@ -1,550 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/**
- * @module engine/conversion/buildviewconverter
- */
-
-import Matcher from '../view/matcher';
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
-import Position from '../model/position';
-import Range from '../model/range';
-
-/**
- * Provides chainable, high-level API to easily build basic view-to-model converters that are appended to given
- * dispatchers. View-to-model converters are used when external data is added to the editor, i.e. when a user pastes
- * HTML content to the editor. Then, converters are used to translate this structure, possibly removing unknown/incorrect
- * nodes, and add it to the model. Also multiple, different elements might be translated into the same thing in the
- * model, i.e. `<b>` and `<strong>` elements might be converted to `bold` attribute (even though `bold` attribute will
- * be then converted only to `<strong>` tag). Instances of this class are created by
- * {@link module:engine/conversion/buildviewconverter~buildViewConverter}.
- *
- * If you need more complex converters, see {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher},
- * {@link module:engine/conversion/view-to-model-converters}, {@link module:engine/conversion/viewconsumable~ViewConsumable}.
- *
- * Using this API it is possible to create various kind of converters:
- *
- * 1. View element to model element:
- *
- *		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
- *
- * 2. View element to model attribute:
- *
- *		buildViewConverter().for( dispatcher ).fromElement( 'b' ).fromElement( 'strong' ).toAttribute( 'bold', 'true' );
- *
- * 3. View attribute to model attribute:
- *
- *		buildViewConverter().for( dispatcher ).fromAttribute( 'style', { 'font-weight': 'bold' } ).toAttribute( 'bold', 'true' );
- *		buildViewConverter().for( dispatcher )
- *			.fromAttribute( 'class' )
- *			.toAttribute( ( viewElement ) => ( { class: viewElement.getAttribute( 'class' ) } ) );
- *
- * 4. View elements and attributes to model attribute:
- *
- *		buildViewConverter().for( dispatcher )
- *			.fromElement( 'b' ).fromElement( 'strong' ).fromAttribute( 'style', { 'font-weight': 'bold' } )
- *			.toAttribute( 'bold', 'true' );
- *
- * 5. View {@link module:engine/view/matcher~Matcher view element matcher instance} or
- * {@link module:engine/view/matcher~Matcher#add matcher pattern}
- * to model element or attribute:
- *
- *		const matcher = new ViewMatcher();
- *		matcher.add( 'div', { class: 'quote' } );
- *		buildViewConverter().for( dispatcher ).from( matcher ).toElement( 'quote' );
- *
- *		buildViewConverter().for( dispatcher ).from( { name: 'span', class: 'bold' } ).toAttribute( 'bold', 'true' );
- *
- * Note, that converters built using `ViewConverterBuilder` automatically check {@link module:engine/model/schema~Schema schema}
- * if created model structure is valid. If given conversion would be invalid according to schema, it is ignored.
- *
- * It is possible to provide creator functions as parameters for {@link ~ViewConverterBuilder#toElement}
- * and {@link module:engine/conversion/buildviewconverter~ViewConverterBuilder#toAttribute} methods. See their descriptions to learn more.
- *
- * By default, converter will {@link module:engine/conversion/viewconsumable~ViewConsumable#consume consume} every value specified in
- * given `from...` query, i.e. `.from( { name: 'span', class: 'bold' } )` will make converter consume both `span` name
- * and `bold` class. It is possible to change this behavior using {@link ~ViewConverterBuilder#consuming consuming}
- * modifier. The modifier alters the last `fromXXX` query used before it. To learn more about consuming values,
- * see {@link module:engine/conversion/viewconsumable~ViewConsumable}.
- *
- * It is also possible to {@link module:engine/conversion/buildviewconverter~ViewConverterBuilder#withPriority change default priority}
- * of created converters to decide which converter should be fired earlier and which later. This is useful if you provide
- * a general converter but want to provide different converter for a specific-case (i.e. given view element is converted
- * always to given model element, but if it has given class it is converter to other model element). For this,
- * use {@link module:engine/conversion/buildviewconverter~ViewConverterBuilder#withPriority withPriority} modifier. The modifier alters
- * the last `from...` query used before it.
- *
- * Note that `to...` methods are "terminators", which means that should be the last one used in building converter.
- *
- * You can use {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder}
- * to create "opposite" converters - from model to view.
- */
-class ViewConverterBuilder {
-	/**
-	 * Creates `ViewConverterBuilder` with given `dispatchers` registered to it.
-	 */
-	constructor() {
-		/**
-		 * Dispatchers to which converters will be attached.
-		 *
-		 * @type {Array.<module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>}
-		 * @private
-		 */
-		this._dispatchers = [];
-
-		/**
-		 * Stores "from" queries.
-		 *
-		 * @type {Array}
-		 * @private
-		 */
-		this._from = [];
-	}
-
-	/**
-	 * Set one or more dispatchers which the built converter will be attached to.
-	 *
-	 * @chainable
-	 * @param {...module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher} dispatchers One or more dispatchers.
-	 * @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder}
-	 */
-	for( ...dispatchers ) {
-		this._dispatchers = dispatchers;
-
-		return this;
-	}
-
-	/**
-	 * Registers what view element should be converted.
-	 *
-	 *		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-	 *
-	 * @chainable
-	 * @param {String} elementName View element name.
-	 * @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder}
-	 */
-	fromElement( elementName ) {
-		return this.from( { name: elementName } );
-	}
-
-	/**
-	 * Registers what view attribute should be converted.
-	 *
-	 *		buildViewConverter().for( dispatcher ).fromAttribute( 'style', { 'font-weight': 'bold' } ).toAttribute( 'bold', 'true' );
-	 *
-	 * @chainable
-	 * @param {String|RegExp} key View attribute key.
-	 * @param {String|RegExp} [value] View attribute value.
-	 * @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder}
-	 */
-	fromAttribute( key, value = /.*/ ) {
-		const pattern = {};
-
-		if ( key === 'style' || key === 'class' ) {
-			pattern[ key ] = value;
-		} else {
-			pattern.attribute = {};
-			pattern.attribute[ key ] = value;
-		}
-
-		const matcher = new Matcher( pattern );
-
-		this._from.push( {
-			matcher,
-			consume: false,
-			priority: null,
-			attributeKey: key
-		} );
-
-		return this;
-	}
-
-	/**
-	 * Registers what view pattern should be converted. The method accepts either {@link module:engine/view/matcher~Matcher view matcher}
-	 * or view matcher pattern.
-	 *
-	 *		const matcher = new ViewMatcher();
-	 *		matcher.add( 'div', { class: 'quote' } );
-	 *		buildViewConverter().for( dispatcher ).from( matcher ).toElement( 'quote' );
-	 *
-	 *		buildViewConverter().for( dispatcher ).from( { name: 'span', class: 'bold' } ).toAttribute( 'bold', 'true' );
-	 *
-	 * @chainable
-	 * @param {Object|module:engine/view/matcher~Matcher} matcher View matcher or view matcher pattern.
-	 * @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder}
-	 */
-	from( matcher ) {
-		if ( !( matcher instanceof Matcher ) ) {
-			matcher = new Matcher( matcher );
-		}
-
-		this._from.push( {
-			matcher,
-			consume: false,
-			priority: null
-		} );
-
-		return this;
-	}
-
-	/**
-	 * Modifies which consumable values will be {@link module:engine/conversion/viewconsumable~ViewConsumable#consume consumed}
-	 * by built converter.
-	 * It modifies the last `from...` query. Can be used after each `from...` query in given chain. Useful for providing
-	 * more specific matches.
-	 *
-	 *		// This converter will only handle class bold conversion (to proper attribute) but span element
-	 *		// conversion will have to be done in separate converter.
-	 *		// Without consuming modifier, the converter would consume both class and name, so a converter for
-	 *		// span element would not be fired.
-	 *		buildViewConverter().for( dispatcher )
-	 *			.from( { name: 'span', class: 'bold' } ).consuming( { class: 'bold' } )
-	 *			.toAttribute( 'bold', 'true' } );
-	 *
-	 *		buildViewConverter().for( dispatcher )
-	 *			.fromElement( 'img' ).consuming( { name: true, attribute: [ 'src', 'title' ] } )
-	 *			.toElement( ( viewElement ) => new ModelElement( 'image', { src: viewElement.getAttribute( 'src' ),
-	 *			                                                            title: viewElement.getAttribute( 'title' ) } );
-	 *
-	 * **Note:** All and only values from passed object has to be consumable on converted view element. This means that
-	 * using `consuming` method, you can either make looser conversion conditions (like in first example) or tighter
-	 * conversion conditions (like in second example). So, the view element, to be converter, has to match query of
-	 * `from...` method and then have to have enough consumable values to consume.
-	 *
-	 * @see module:engine/conversion/viewconsumable~ViewConsumable
-	 * @chainable
-	 * @param {Object} consume Values to consume.
-	 * @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder}
-	 */
-	consuming( consume ) {
-		const lastFrom = this._from[ this._from.length - 1 ];
-		lastFrom.consume = consume;
-
-		return this;
-	}
-
-	/**
-	 * Changes default priority for built converter. It modifies the last `from...` query. Can be used after each
-	 * `from...` query in given chain. Useful for overwriting converters. The lower the number, the earlier converter will be fired.
-	 *
-	 *		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-	 *		// Register converter with proper priority, otherwise "p" element would get consumed by first
-	 *		// converter and the second converter would not be fired.
-	 *		buildViewConverter().for( dispatcher )
-	 *			.from( { name: 'p', class: 'custom' } ).withPriority( 9 )
-	 *			.toElement( 'customParagraph' );
-	 *
-	 * **Note:** `ViewConverterBuilder` takes care of applying all `toElement()` conversions before all `toAttribute()`
-	 * conversions. This is done by setting default `toElement()` priority to `normal` and `toAttribute()` priority to `low`.
-	 * It is recommended to set converter priority for `toElement()` around `0` (the value of `normal` priority)
-	 * and `toAttribute()` priority around `-1000` (the value of `low` priority).
-	 * It is important that model elements are created before attributes, otherwise attributes would
-	 * not be applied or other errors may occur.
-	 *
-	 * @chainable
-	 * @param {Number} priority Converter priority.
-	 * @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder}
-	 */
-	withPriority( priority ) {
-		const lastFrom = this._from[ this._from.length - 1 ];
-		lastFrom.priority = priority;
-
-		return this;
-	}
-
-	/**
-	 * Registers what model element will be created by converter.
-	 *
-	 * Method accepts two ways of providing what kind of model element will be created. You can pass model element
-	 * name as a `string` or a function that will return model element instance. If you provide creator function,
-	 * it will be passed converted view element as first and only parameter.
-	 *
-	 *		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-	 *		buildViewConverter().for( dispatcher )
-	 *			.fromElement( 'img' )
-	 *			.toElement( ( viewElement, writer ) => writer.createElement( 'image', { src: viewElement.getAttribute( 'src' ) } ) );
-	 *
-	 * @param {String|Function} element Model element name or model element creator function.
-	 */
-	toElement( element ) {
-		function eventCallbackGen( from ) {
-			return ( evt, data, conversionApi ) => {
-				const writer = conversionApi.writer;
-
-				// 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.viewItem );
-
-				// If there is no match, this callback should not do anything.
-				if ( !matchAll ) {
-					return;
-				}
-
-				// Now, for every match between matcher and actual element, we will try to consume the match.
-				for ( const match of matchAll ) {
-					// Create model element basing on creator function or element name.
-					const modelElement = element instanceof Function ? element( data.viewItem, writer ) : writer.createElement( element );
-
-					// Do not convert if element building function returned falsy value.
-					if ( !modelElement ) {
-						continue;
-					}
-
-					// When element was already consumed then skip it.
-					if ( !conversionApi.consumable.test( data.viewItem, from.consume || match.match ) ) {
-						continue;
-					}
-
-					// Find allowed parent for element that we are going to insert.
-					// If current parent does not allow to insert element but one of the ancestors does
-					// then split nodes to allowed parent.
-					const splitResult = conversionApi.splitToAllowedParent( modelElement, data.modelCursor );
-
-					// When there is no split result it means that we can't insert element to model tree, so let's skip it.
-					if ( !splitResult ) {
-						continue;
-					}
-
-					// Insert element on allowed position.
-					conversionApi.writer.insert( modelElement, splitResult.position );
-
-					// Convert children and insert to element.
-					const childrenResult = conversionApi.convertChildren( data.viewItem, Position.createAt( modelElement ) );
-
-					// Consume appropriate value from consumable values list.
-					conversionApi.consumable.consume( data.viewItem, from.consume || match.match );
-
-					// Set conversion result range.
-					data.modelRange = new Range(
-						// Range should start before inserted element
-						Position.createBefore( modelElement ),
-						// Should end after but we need to take into consideration that children could split our
-						// element, so we need to move range after parent of the last converted child.
-						// before: <allowed>[]</allowed>
-						// after: <allowed>[<converted><child></child></converted><child></child><converted>]</converted></allowed>
-						Position.createAfter( childrenResult.modelCursor.parent )
-					);
-
-					// Now we need to check where the modelCursor should be.
-					// If we had to split parent to insert our element then we want to continue conversion inside split parent.
-					//
-					// before: <allowed><notAllowed>[]</notAllowed></allowed>
-					// after:  <allowed><notAllowed></notAllowed><converted></converted><notAllowed>[]</notAllowed></allowed>
-					if ( splitResult.cursorParent ) {
-						data.modelCursor = Position.createAt( splitResult.cursorParent );
-
-					// Otherwise just continue after inserted element.
-					} else {
-						data.modelCursor = data.modelRange.end;
-					}
-
-					// Prevent multiple conversion if there are other correct matches.
-					break;
-				}
-			};
-		}
-
-		this._setCallback( eventCallbackGen, 'normal' );
-	}
-
-	/**
-	 * Registers what model attribute will be created by converter.
-	 *
-	 * Method accepts two ways of providing what kind of model attribute will be created. You can either pass two strings
-	 * representing attribute key and attribute value or a function that returns an object with `key` and `value` properties.
-	 * If you provide creator function, it will be passed converted view element as first and only parameter.
-	 *
-	 *		buildViewConverter().for( dispatcher ).fromAttribute( 'alt' ).toAttribute( 'alt' );
-	 *		buildViewConverter().for( dispatcher ).fromAttribute( 'style', { 'font-weight': 'bold' } ).toAttribute( 'bold', true );
-	 *		buildViewConverter().for( dispatcher )
-	 *			.fromAttribute( 'class' )
-	 *			.toAttribute( ( viewElement ) => ( { key: 'class', value: 'class-' + viewElement.getAttribute( 'class' ) } ) );
-	 *
-	 * @param {String|Function} keyOrCreator Attribute key or a creator function.
-	 * @param {String} [value] Attribute value. Ignored if `keyOrCreator` is not a `string`. If `keyOrCreator` is `string`,
-	 * if `value` is not set, attribute value from converted element will be used.
-	 */
-	toAttribute( keyOrCreator, value ) {
-		function eventCallbackGen( from ) {
-			return ( evt, data, conversionApi ) => {
-				// 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.viewItem );
-
-				// If there is no match, this callback should not do anything.
-				if ( !matchAll ) {
-					return;
-				}
-
-				// 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 ( !conversionApi.consumable.consume( data.viewItem, from.consume || match.match ) ) {
-						continue;
-					}
-
-					// Since we are converting to attribute we need an range on which we will set the attribute.
-					// If the range is not created yet, we will create it.
-					if ( !data.modelRange ) {
-						// Convert children and set conversion result as a current data.
-						data = Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
-					}
-
-					// Use attribute creator function, if provided.
-					let attribute;
-
-					if ( keyOrCreator instanceof Function ) {
-						attribute = keyOrCreator( data.viewItem );
-
-						if ( !attribute ) {
-							return;
-						}
-					} else {
-						attribute = {
-							key: keyOrCreator,
-							value: value ? value : data.viewItem.getAttribute( from.attributeKey )
-						};
-					}
-
-					// Set attribute on each item in range according to Schema.
-					for ( const node of Array.from( data.modelRange.getItems() ) ) {
-						if ( conversionApi.schema.checkAttribute( node, attribute.key ) ) {
-							conversionApi.writer.setAttribute( attribute.key, attribute.value, node );
-						}
-					}
-
-					// Prevent multiple conversion if there are other correct matches.
-					break;
-				}
-			};
-		}
-
-		this._setCallback( eventCallbackGen, 'low' );
-	}
-
-	/**
-	 * Registers how model element marking marker range will be created by converter.
-	 *
-	 * Created element has to match the following pattern:
-	 *
-	 * 		{ name: '$marker', attribute: { data-name: /^\w/ } }
-	 *
-	 * There are two ways of creating this element:
-	 *
-	 * 1. Makes sure that converted view element will have property `data-name` then converter will
-	 * automatically take this property value. In this case there is no need to provide creator function.
-	 * For the following view:
-	 *
-	 *		<marker data-name="search"></marker>foo<marker data-name="search"></marker>
-	 *
-	 * converter should look like this:
-	 *
-	 *		buildViewConverter().for( dispatcher ).fromElement( 'marker' ).toMarker();
-	 *
-	 * 2. Creates element by creator:
-	 *
-	 * For the following view:
-	 *
-	 * 		<span foo="search"></span>foo<span foo="search"></span>
-	 *
-	 * converter should look like this:
-	 *
-	 * 		buildViewConverter().for( dispatcher ).from( { name: 'span', { attribute: foo: /^\w/ } } ).toMarker( ( data ) => {
-	 * 			return new Element( '$marker', { 'data-name': data.getAttribute( 'foo' ) } );
-	 * 		} );
-	 *
-	 * @param {Function} [creator] Creator function.
-	 */
-	toMarker( creator ) {
-		function eventCallbackGen( from ) {
-			return ( evt, data, conversionApi ) => {
-				const writer = conversionApi.writer;
-
-				// 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.viewItem );
-
-				// 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.viewItem );
-				// When there is no creator then create model element basing on data from view element.
-				} else {
-					modelElement = writer.createElement( '$marker', { 'data-name': data.viewItem.getAttribute( 'data-name' ) } );
-				}
-
-				// Check if model element is correct (has proper name and property).
-				if ( modelElement.name != '$marker' || typeof modelElement.getAttribute( 'data-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 ( !conversionApi.consumable.consume( data.viewItem, from.consume || match.match ) ) {
-						continue;
-					}
-
-					// Tmp fix because multiple matchers are not properly matched and consumed.
-					// See https://github.com/ckeditor/ckeditor5-engine/issues/1257.
-					if ( data.modelRange ) {
-						continue;
-					}
-
-					writer.insert( modelElement, data.modelCursor );
-					data.modelRange = Range.createOn( modelElement );
-					data.modelCursor = data.modelRange.end;
-
-					// 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.
-	 *
-	 * @param eventCallbackGen
-	 * @param defaultPriority
-	 * @private
-	 */
-	_setCallback( eventCallbackGen, defaultPriority ) {
-		// We will add separate event callback for each registered `from` entry.
-		for ( const from of this._from ) {
-			// We have to figure out event name basing on matcher's patterns.
-			// If there is exactly one pattern and it has `name` property we will used that name.
-			const matcherElementName = from.matcher.getElementName();
-			const eventName = matcherElementName ? 'element:' + matcherElementName : 'element';
-			const eventCallback = eventCallbackGen( from );
-
-			const priority = from.priority === null ? defaultPriority : from.priority;
-
-			// Add event to each registered dispatcher.
-			for ( const dispatcher of this._dispatchers ) {
-				dispatcher.on( eventName, eventCallback, { priority } );
-			}
-		}
-	}
-}
-
-/**
- * Entry point for view-to-model converters builder. This chainable API makes it easy to create basic, most common
- * view-to-model converters and attach them to provided dispatchers. The method returns an instance of
- * {@link module:engine/conversion/buildviewconverter~ViewConverterBuilder}.
- */
-export default function buildViewConverter() {
-	return new ViewConverterBuilder();
-}

+ 0 - 347
packages/ckeditor5-engine/src/conversion/definition-based-converters.js

@@ -1,347 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/**
- * @module engine/conversion/definition-based-converters
- */
-
-import AttributeElement from '../view/attributeelement';
-import ViewContainerElement from '../view/containerelement';
-
-import buildModelConverter from './buildmodelconverter';
-import buildViewConverter from './buildviewconverter';
-
-/**
- * Helper for creating a model element to {@link module:engine/view/containerelement~ContainerElement view container element}
- * converters.
- *
- * You can create a converter by using a simplified converter definition:
- *
- *		modelElementToViewContainerElement( {
- *			model: 'heading1',
- *			view: 'h1',
- *		}, [ editor.editing.modelToView, editor.data.modelToView ] );
- *
- * Or by using a full-flavored view object:
- *
- *		modelElementToViewContainerElement( {
- *			model: 'heading1',
- *			view: {
- *				name: 'h1',
- *				class: [ 'header', 'article-header' ],
- *				attribute: {
- *					data-header: 'level-1',
- *				}
- *			},
- *		}, [ editor.editing.modelToView, editor.data.modelToView ] );
- *
- * The above will generate the following view element:
- *
- *		<h1 class="header article-header" data-header="level-1">...</h1>
- *
- * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition The converter configuration.
- * @param {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher>} dispatchers
- */
-export function modelElementToViewContainerElement( definition, dispatchers ) {
-	const { model: modelElement, targetView } = normalizeConverterDefinition( definition );
-
-	buildModelConverter()
-		.for( ...dispatchers )
-		.fromElement( modelElement )
-		.toElement( () => createViewElementFromDefinition( targetView, ViewContainerElement ) );
-}
-
-/**
- * Helper for creating a view element to model element converters.
- *
- * Besides converting the view element specified in the `view` property it will also convert all view elements
- * which match the patterns defined in the `acceptAlso` property. Such a "wide" converters are often needed so the editor
- * is able to correctly handle a content that was pasted into the editor. Such pasted content may use various
- * "flavors" of the same editing features (e.g. you may want to handle `<h1>` and `<p class="heading1">` as headings).
- *
- * The `model` property defines the model element name to be used by the converter.
- *
- * A converter can be defined using a simplified converter definition:
- *
- *		viewToModelElement( { model: 'heading1', view: 'h1' }, [ dispatcher ] );
- *
- * Or by using a full-flavored definition:
- *
- *		viewToModelElement( {
- *			model: 'heading1',
- *			view: {
- *				name: 'p',
- *				attribute: {
- *					'data-heading': 'true'
- *				},
- *				// You may need to use a high-priority listener to catch elements
- *				// which are handled by other (usually – more generic) converters too.
- *				priority: 'high'
- *			}
- *		}, [ editor.data.viewToModel ] );
- *
- * Or with the `acceptAlso` property to match more patterns:
- *
- *		viewToModelElement( {
- *			model: 'heading1',
- *			view: 'h1',
- *			acceptAlso: [
- *				{ name: 'p', attribute: { 'data-heading': 'level1' }, priority: 'high' },
- *				{ name: 'h2', class: 'heading-main' },
- *				{ name: 'div', style: { 'font-weight': 'bold', font-size: '24px' } }
- *			]
- *		}, [ editor.data.viewToModel ] );
- *
- * The above example will convert an existing view elements:
- *
- *		<h1>A heading</h1>
- *		<h2 class="heading-main">Another heading</h2>
- *		<p data-heading="level1">Paragraph-like heading</p>
- *		<div style="font-size:24px; font-weigh:bold;">Another non-semantic header</div>
- *
- * into `heading1` model elements so in model it will be represented as:
- *
- *		<heading1>A heading</heading1>
- *		<heading1>Another heading</heading1>
- *		<heading1>Paragraph-like heading</heading1>
- *		<heading1>Another non-semantic header</heading1>
- *
- * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration.
- * @param {Array.<module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers
- */
-export function viewToModelElement( definition, dispatchers ) {
-	const { model: modelElement, sourceViews } = normalizeConverterDefinition( definition );
-
-	const converter = prepareViewConverter( dispatchers, sourceViews );
-
-	converter.toElement( modelElement );
-}
-
-/**
- * Helper for creating a model attribute to {@link module:engine/view/attributeelement~AttributeElement view attribute element}
- * converters.
- *
- * A converter can be defined by using a simplified converter definition:
- *
- *		modelAttributeToViewAttributeElement( 'bold', [
- *			{
- *				model: 'true',
- *				view: 'strong'
- *			}
- *		], [ editor.editing.modelToView, editor.data.modelToView ] );
- *
- * Or defining full-flavored view objects:
- *
- *		modelAttributeToViewAttributeElement( 'fontSize', [
- *			{
- *				model: 'big',
- *				view: {
- *					name: 'span',
- *					style: { 'font-size': '1.2em' }
- *				},
- *			},
- *			{
- *				model: 'small',
- *				view: {
- *					name: 'span',
- *					style: { 'font-size': '0.8em' }
- *				},
- *			}
- *		], [ editor.editing.modelToView, editor.data.modelToView ] );
- *
- * The above will generate the following view element from a `fontSize="big"` model attribute:
- *
- *		<span style="font-size: 1.2em">...</span>
- *
- * @param {String} attributeName The name of the model attribute which should be converted.
- * @param {Array.<module:engine/conversion/definition-based-converters~ConverterDefinition>} definitions A conversion configuration objects
- * for each possible attribute value.
- * @param {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher>} dispatchers
- */
-export function modelAttributeToViewAttributeElement( attributeName, definitions, dispatchers ) {
-	// Create a map of attributeValue - to - ViewElementDefinition.
-	const valueToTargetViewMap = definitions
-		.map( normalizeConverterDefinition )
-		.reduce( ( mapObject, normalizedDefinition ) => {
-			mapObject[ normalizedDefinition.model ] = normalizedDefinition.targetView;
-
-			return mapObject;
-		}, {} );
-
-	buildModelConverter()
-		.for( ...dispatchers )
-		.fromAttribute( attributeName )
-		.toElement( value => {
-			const targetView = valueToTargetViewMap[ value ];
-
-			if ( !targetView ) {
-				return;
-			}
-
-			return createViewElementFromDefinition( targetView, AttributeElement );
-		} );
-}
-
-/**
- * Helper for creating view element to model attribute converters.
- *
- * Besides converting the view element specified in the `view` property it will also convert all view elements
- * which match the patterns defined in the `acceptAlso` property. Such "wide" converters are often needed so the editor
- * is able to correctly handle a content that was pasted into the editor. Such pasted content may use various
- * "flavors" of the same editing features (e.g. bold might be represented as `<b>`, `<strong>` or
- * `<span style="font-weight:bold">`).
- *
- * The `model` property defines the value of the model attribute.
- *
- * A converter can be defined using a simplified converter definition:
- *
- *		viewToModelAttribute( 'bold', { model: true, view: 'strong' }, [ dispatcher ] );
- *
- * Or by using a full-flavored definition:
- *
- *		viewToModelAttribute( 'fontSize', {
- *			model: 'big',
- *			view: {
- *				name: 'span',
- *				style: {
- *					'font-size': '1.2em'
- *				}
- *			}
- *		}, [ editor.data.viewToModel ] );
- *
- * Or with the `acceptAlso` property to match more patterns:
- *
- *		viewToModelAttribute( 'fontSize', {
- *			model: 'big',
- *			view: {
- *				name: 'span',
- *				class: 'text-big'
- *			},
- *			acceptAlso: [
- *				{ name: 'span', attribute: { 'data-size': 'big' } },
- *				{ name: 'span', class: [ 'font', 'font-huge' ] },
- *				{ name: 'span', style: { font-size: '18px' } }
- *			]
- *		}, [ editor.data.viewToModel ] );
- *
- * The above example will convert the following view elements:
- *
- *		<p>
- *			An example text with some big elements:
- *			<span class="text-big">one</span>,
- *			<span data-size="big">two</span>,
- *			<span class="font font-huge">three</span>,
- *			<span style="font-size: 18px">four</span>
- *		</p>
- *
- * to a `fontSize="big"` model attribute:
- *
- *		<paragraph>
- *			An example text with some big elements:
- *			<$text fontSize="big">one</$text>,
- *			<$text fontSize="big">two</$text>,
- *			<$text fontSize="big">three</$text>,
- *			<$text fontSize="big">four</$text>
- *		</paragraph>
- *
- * @param {String} attributeName Attribute name to which convert.
- * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration.
- * @param {Array.<module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers
- */
-export function viewToModelAttribute( attributeName, definition, dispatchers ) {
-	const { model: attributeValue, sourceViews } = normalizeConverterDefinition( definition );
-
-	const converter = prepareViewConverter( dispatchers, sourceViews );
-
-	converter.toAttribute( () => ( {
-		key: attributeName,
-		value: attributeValue
-	} ) );
-}
-
-// Normalize a {@link module:engine/conversion/definition-based-converters~ConverterDefinition}
-// into internal object used when building converters.
-//
-// @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition An object that defines view to model
-// and model to view conversion.
-// @returns {Object}
-function normalizeConverterDefinition( definition ) {
-	const model = definition.model;
-	const view = definition.view;
-
-	// View definition might be defined as a name of an element.
-	const targetView = typeof view == 'string' ? { name: view } : view;
-
-	const sourceViews = Array.from( definition.acceptsAlso ? definition.acceptsAlso : [] );
-
-	// Source views also accepts default view definition used in model-to-view conversion.
-	sourceViews.push( targetView );
-
-	return { model, targetView, sourceViews };
-}
-
-// Helper method for preparing a view converter from passed view definitions.
-//
-// @param {Array.<module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers
-// @param {Array.<module:engine/view/viewelementdefinition~ViewElementDefinition>} viewDefinitions
-// @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder}
-function prepareViewConverter( dispatchers, viewDefinitions ) {
-	const converter = buildViewConverter().for( ...dispatchers );
-
-	for ( const viewDefinition of viewDefinitions ) {
-		converter.from( Object.assign( {}, viewDefinition ) );
-
-		if ( viewDefinition.priority ) {
-			converter.withPriority( viewDefinition.priority );
-		}
-	}
-
-	return converter;
-}
-
-// Creates view element instance from provided viewElementDefinition and class.
-//
-// @param {module:engine/view/viewelementdefinition~ViewElementDefinition} viewElementDefinition
-// @param {Function} ViewElementClass
-// @returns {module:engine/view/element~Element}
-function createViewElementFromDefinition( viewElementDefinition, ViewElementClass ) {
-	const element = new ViewElementClass( viewElementDefinition.name, Object.assign( {}, viewElementDefinition.attribute ) );
-
-	if ( viewElementDefinition.style ) {
-		element.setStyle( viewElementDefinition.style );
-	}
-
-	const classes = viewElementDefinition.class;
-
-	if ( classes ) {
-		element.addClass( ... typeof classes === 'string' ? [ classes ] : classes );
-	}
-
-	return element;
-}
-
-/**
- * Defines conversion details. It is used in configuration-based converters:
- *
- * * {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement}
- * * {@link module:engine/conversion/definition-based-converters~modelElementToViewContainerElement}
- * * {@link module:engine/conversion/definition-based-converters~viewToModelAttribute}
- * * {@link module:engine/conversion/definition-based-converters~viewToModelElement}
- *
- * See the above converters for examples how to use the converter definition.
- *
- * @typedef {Object} ConverterDefinition
- * @property {String} model Defines to model conversion. When using to element conversion
- * ({@link module:engine/conversion/definition-based-converters~viewToModelElement}
- * and {@link module:engine/conversion/definition-based-converters~modelElementToViewContainerElement})
- * it defines element name. When using to attribute conversion
- * ({@link module:engine/conversion/definition-based-converters~viewToModelAttribute}
- * and {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement})
- * it defines attribute value to which it is converted.
- * @property {module:engine/view/viewelementdefinition~ViewElementDefinition} view Defines model to view conversion and is also used
- * in view to model conversion pipeline.
- * @property {Array.<module:engine/view/viewelementdefinition~ViewElementDefinition>} acceptAlso An array with all matched elements that
- * view to model conversion should also accepts.
- */

+ 31 - 24
packages/ckeditor5-engine/tests/controller/datacontroller.js

@@ -8,11 +8,7 @@ import Range from '../../src/model/range';
 import DataController from '../../src/controller/datacontroller';
 import HtmlDataProcessor from '../../src/dataprocessor/htmldataprocessor';
 
-import buildViewConverter from '../../src/conversion/buildviewconverter';
-import buildModelConverter from '../../src/conversion/buildmodelconverter';
-
 import ModelDocumentFragment from '../../src/model/documentfragment';
-
 import ViewDocumentFragment from '../../src/view/documentfragment';
 
 import { getData, setData, stringify, parse as parseModel } from '../../src/dev-utils/model';
@@ -20,6 +16,17 @@ import { parse as parseView, stringify as stringifyView } from '../../src/dev-ut
 
 import count from '@ckeditor/ckeditor5-utils/src/count';
 
+import {
+	elementToElement as vtmElementToElement,
+	elementToAttribute as vtmElementToAttribute
+} from '../../src/conversion/view-to-model-helpers';
+
+import {
+	elementToElement as mtvElementToElement,
+	attributeToElement as mtvAttributeToElement,
+	markerToHighlight as mtvMarkerToHighlight
+} from '../../src/conversion/model-to-view-helpers';
+
 describe( 'DataController', () => {
 	let model, modelDocument, htmlDataProcessor, data, schema;
 
@@ -59,7 +66,7 @@ describe( 'DataController', () => {
 		it( 'should set paragraph', () => {
 			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
-			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
+			vtmElementToElement( { view: 'p', model: 'paragraph' } )( data.viewToModel );
 
 			const output = data.parse( '<p>foo<b>bar</b></p>' );
 
@@ -70,7 +77,7 @@ describe( 'DataController', () => {
 		it( 'should set two paragraphs', () => {
 			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
-			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
+			vtmElementToElement( { view: 'p', model: 'paragraph' } )( data.viewToModel );
 
 			const output = data.parse( '<p>foo</p><p>bar</p>' );
 
@@ -84,10 +91,10 @@ describe( 'DataController', () => {
 				allowAttributes: [ 'bold' ]
 			} );
 
-			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
-			buildViewConverter().for( data.viewToModel ).fromElement( 'b' ).toAttribute( 'bold', true );
+			vtmElementToElement( { view: 'p', model: 'paragraph' } )( data.viewToModel );
+			vtmElementToAttribute( { view: 'strong', model: 'bold' } )( data.viewToModel );
 
-			const output = data.parse( '<p>foo<b>bar</b></p>' );
+			const output = data.parse( '<p>foo<strong>bar</strong></p>' );
 
 			expect( output ).to.instanceof( ModelDocumentFragment );
 			expect( stringify( output ) ).to.equal( '<paragraph>foo<$text bold="true">bar</$text></paragraph>' );
@@ -110,7 +117,7 @@ describe( 'DataController', () => {
 		beforeEach( () => {
 			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
-			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
+			vtmElementToElement( { view: 'p', model: 'paragraph' } )( data.viewToModel );
 		} );
 
 		it( 'should convert content of an element #1', () => {
@@ -213,7 +220,7 @@ describe( 'DataController', () => {
 			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph>foo</paragraph>' );
 
-			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
+			mtvElementToElement( { model: 'paragraph', view: 'p' } )( data.modelToView );
 
 			expect( data.get() ).to.equal( '<p>foo</p>' );
 		} );
@@ -222,7 +229,7 @@ describe( 'DataController', () => {
 			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph></paragraph>' );
 
-			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
+			mtvElementToElement( { model: 'paragraph', view: 'p' } )( data.modelToView );
 
 			expect( data.get() ).to.equal( '<p>&nbsp;</p>' );
 		} );
@@ -231,7 +238,7 @@ describe( 'DataController', () => {
 			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph>foo</paragraph><paragraph>bar</paragraph>' );
 
-			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
+			mtvElementToElement( { model: 'paragraph', view: 'p' } )( data.modelToView );
 
 			expect( data.get() ).to.equal( '<p>foo</p><p>bar</p>' );
 		} );
@@ -247,7 +254,7 @@ describe( 'DataController', () => {
 			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph>foo<$text bold="true">bar</$text></paragraph>' );
 
-			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
+			mtvElementToElement( { model: 'paragraph', view: 'p' } )( data.modelToView );
 
 			expect( data.get() ).to.equal( '<p>foobar</p>' );
 		} );
@@ -256,10 +263,10 @@ describe( 'DataController', () => {
 			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph>foo<$text bold="true">bar</$text></paragraph>' );
 
-			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
-			buildModelConverter().for( data.modelToView ).fromAttribute( 'bold' ).toElement( 'b' );
+			mtvElementToElement( { model: 'paragraph', view: 'p' } )( data.modelToView );
+			mtvAttributeToElement( 'bold', { view: 'strong' } )( data.modelToView );
 
-			expect( data.get() ).to.equal( '<p>foo<b>bar</b></p>' );
+			expect( data.get() ).to.equal( '<p>foo<strong>bar</strong></p>' );
 		} );
 
 		it( 'should get root name as a parameter', () => {
@@ -269,8 +276,8 @@ describe( 'DataController', () => {
 			setData( model, '<paragraph>foo</paragraph>', { rootName: 'main' } );
 			setData( model, 'Bar', { rootName: 'title' } );
 
-			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
-			buildModelConverter().for( data.modelToView ).fromAttribute( 'bold' ).toElement( 'b' );
+			mtvElementToElement( { model: 'paragraph', view: 'p' } )( data.modelToView );
+			mtvAttributeToElement( 'bold', { view: 'strong' } )( data.modelToView );
 
 			expect( data.get() ).to.equal( '<p>foo</p>' );
 			expect( data.get( 'main' ) ).to.equal( '<p>foo</p>' );
@@ -286,7 +293,7 @@ describe( 'DataController', () => {
 			schema.extend( '$block', { allowIn: 'div' } );
 			schema.extend( 'div', { allowIn: '$root' } );
 
-			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
+			mtvElementToElement( { model: 'paragraph', view: 'p' } )( data.modelToView );
 		} );
 
 		it( 'should stringify a content of an element', () => {
@@ -310,7 +317,7 @@ describe( 'DataController', () => {
 			schema.extend( '$block', { allowIn: 'div' } );
 			schema.extend( 'div', { allowIn: '$root' } );
 
-			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
+			mtvElementToElement( { model: 'paragraph', view: 'p' } )( data.modelToView );
 		} );
 
 		it( 'should convert a content of an element', () => {
@@ -331,7 +338,7 @@ describe( 'DataController', () => {
 			const modelElement = parseModel( '<div><paragraph>foobar</paragraph></div>', schema );
 			const modelRoot = model.document.getRoot();
 
-			buildModelConverter().for( data.modelToView ).fromMarker( 'marker:a' ).toHighlight( { class: 'a' } );
+			mtvMarkerToHighlight( { model: 'marker:a', view: { class: 'a' } } )( data.modelToView );
 
 			model.change( writer => {
 				writer.insert( modelElement, modelRoot, 0 );
@@ -348,8 +355,8 @@ describe( 'DataController', () => {
 			const modelElement = parseModel( '<div><paragraph>foo</paragraph><paragraph>bar</paragraph></div>', schema );
 			const modelRoot = model.document.getRoot();
 
-			buildModelConverter().for( data.modelToView ).fromMarker( 'marker:a' ).toHighlight( { class: 'a' } );
-			buildModelConverter().for( data.modelToView ).fromMarker( 'marker:b' ).toHighlight( { class: 'b' } );
+			mtvMarkerToHighlight( { model: 'marker:a', view: { class: 'a' } } )( data.modelToView );
+			mtvMarkerToHighlight( { model: 'marker:b', view: { class: 'b' } } )( data.modelToView );
 
 			const modelP1 = modelElement.getChild( 0 );
 			const modelP2 = modelElement.getChild( 1 );

+ 10 - 7
packages/ckeditor5-engine/tests/controller/editingcontroller.js

@@ -13,7 +13,8 @@ import ViewDocument from '../../src/view/document';
 
 import Mapper from '../../src/conversion/mapper';
 import ModelConversionDispatcher from '../../src/conversion/modelconversiondispatcher';
-import buildModelConverter from '../../src/conversion/buildmodelconverter';
+
+import { elementToElement, markerToHighlight } from '../../src/conversion/model-to-view-helpers';
 
 import Model from '../../src/model/model';
 import ModelPosition from '../../src/model/position';
@@ -89,9 +90,10 @@ describe( 'EditingController', () => {
 
 			model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			model.schema.register( 'div', { inheritAllFrom: '$block' } );
-			buildModelConverter().for( editing.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
-			buildModelConverter().for( editing.modelToView ).fromElement( 'div' ).toElement( 'div' );
-			buildModelConverter().for( editing.modelToView ).fromMarker( 'marker' ).toHighlight( {} );
+
+			elementToElement( { model: 'paragraph', view: 'p' } )( editing.modelToView );
+			elementToElement( { model: 'div', view: 'div' } )( editing.modelToView );
+			markerToHighlight( { model: 'marker', view: {} } )( editing.modelToView );
 
 			// Note: The below code is highly overcomplicated due to #455.
 			model.change( writer => {
@@ -355,9 +357,10 @@ describe( 'EditingController', () => {
 
 			model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			model.schema.register( 'div', { inheritAllFrom: '$block' } );
-			buildModelConverter().for( editing.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
-			buildModelConverter().for( editing.modelToView ).fromElement( 'div' ).toElement( 'div' );
-			buildModelConverter().for( editing.modelToView ).fromMarker( 'marker' ).toHighlight( {} );
+
+			elementToElement( { model: 'paragraph', view: 'p' } )( editing.modelToView );
+			elementToElement( { model: 'div', view: 'div' } )( editing.modelToView );
+			markerToHighlight( { model: 'marker', view: {} } )( editing.modelToView );
 
 			const modelData = new ModelDocumentFragment( parse(
 				'<paragraph>foo</paragraph>' +

+ 0 - 604
packages/ckeditor5-engine/tests/conversion/buildmodelconverter.js

@@ -1,604 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import buildModelConverter from '../../src/conversion/buildmodelconverter';
-
-import Model from '../../src/model/model';
-import ModelElement from '../../src/model/element';
-import ModelText from '../../src/model/text';
-import ModelRange from '../../src/model/range';
-import ModelPosition from '../../src/model/position';
-
-import ViewElement from '../../src/view/element';
-import ViewContainerElement from '../../src/view/containerelement';
-import ViewAttributeElement from '../../src/view/attributeelement';
-import ViewUIElement from '../../src/view/uielement';
-import ViewText from '../../src/view/text';
-
-import EditingController from '../../src/controller/editingcontroller';
-
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
-
-function viewAttributesToString( item ) {
-	let result = '';
-
-	for ( const key of item.getAttributeKeys() ) {
-		const value = item.getAttribute( key );
-
-		if ( value ) {
-			result += ' ' + key + '="' + value + '"';
-		}
-	}
-
-	return result;
-}
-
-function viewToString( item ) {
-	let result = '';
-
-	if ( item instanceof ViewText ) {
-		result = item.data;
-	} else {
-		// ViewElement or ViewDocumentFragment.
-		for ( const child of item.getChildren() ) {
-			result += viewToString( child );
-		}
-
-		if ( item instanceof ViewElement ) {
-			result = '<' + item.name + viewAttributesToString( item ) + '>' + result + '</' + item.name + '>';
-		}
-	}
-
-	return result;
-}
-
-describe( 'Model converter builder', () => {
-	let dispatcher, modelDoc, modelRoot, viewRoot, viewSelection, model, controller;
-
-	beforeEach( () => {
-		model = new Model();
-		modelDoc = model.document;
-		modelRoot = modelDoc.createRoot();
-
-		controller = new EditingController( model );
-
-		// Set name of view root the same as dom root.
-		// This is a mock of attaching view root to dom root.
-		controller.view.getRoot()._name = 'div';
-
-		dispatcher = controller.modelToView;
-
-		viewRoot = controller.view.getRoot();
-		viewSelection = controller.view.selection;
-
-		buildModelConverter().for( dispatcher ).fromElement( 'paragraph' ).toElement( 'p' );
-	} );
-
-	describe( 'model element to view element conversion', () => {
-		it( 'using passed view element name', () => {
-			const modelElement = new ModelElement( 'paragraph', null, new ModelText( 'foobar' ) );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-		} );
-
-		it( 'using passed view element', () => {
-			buildModelConverter().for( dispatcher ).fromElement( 'image' ).toElement( new ViewContainerElement( 'img' ) );
-
-			const modelElement = new ModelElement( 'image' );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><img></img></div>' );
-		} );
-
-		it( 'using passed creator function', () => {
-			buildModelConverter().for( dispatcher )
-				.fromElement( 'header' )
-				.toElement( data => new ViewContainerElement( 'h' + data.item.getAttribute( 'level' ) ) );
-
-			const modelElement = new ModelElement( 'header', { level: 2 }, new ModelText( 'foobar' ) );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><h2>foobar</h2></div>' );
-		} );
-	} );
-
-	describe( 'model attribute to view element conversion', () => {
-		it( 'using passed view element name', () => {
-			buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).toElement( 'strong' );
-
-			const modelElement = new ModelText( 'foo', { bold: true } );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><strong>foo</strong></div>' );
-
-			model.change( writer => {
-				writer.removeAttribute( 'bold', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div>foo</div>' );
-		} );
-
-		it( 'using passed view element', () => {
-			buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).toElement( new ViewAttributeElement( 'strong' ) );
-
-			const modelElement = new ModelText( 'foo', { bold: true } );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><strong>foo</strong></div>' );
-
-			model.change( writer => {
-				writer.removeAttribute( 'bold', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div>foo</div>' );
-		} );
-
-		it( 'using passed creator function', () => {
-			buildModelConverter().for( dispatcher ).fromAttribute( 'italic' ).toElement( value => new ViewAttributeElement( value ) );
-
-			const modelElement = new ModelText( 'foo', { italic: 'em' } );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><em>foo</em></div>' );
-
-			model.change( writer => {
-				writer.setAttribute( 'italic', 'i', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><i>foo</i></div>' );
-
-			model.change( writer => {
-				writer.removeAttribute( 'italic', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div>foo</div>' );
-		} );
-
-		it( 'selection conversion', () => {
-			// Model converter builder should add selection converter.
-			buildModelConverter().for( dispatcher ).fromAttribute( 'italic' ).toElement( value => new ViewAttributeElement( value ) );
-
-			model.change( writer => {
-				writer.insert( new ModelText( 'foo', { italic: 'em' } ), ModelPosition.createAt( modelRoot, 0 ) );
-
-				// Set collapsed selection after "f".
-				const position = new ModelPosition( modelRoot, [ 1 ] );
-				writer.setSelection( new ModelRange( position, position ) );
-			} );
-
-			// Check if view structure is ok.
-			expect( viewToString( viewRoot ) ).to.equal( '<div><em>foo</em></div>' );
-
-			// Check if view selection is collapsed after "f" letter.
-			let ranges = Array.from( viewSelection.getRanges() );
-			expect( ranges.length ).to.equal( 1 );
-			expect( ranges[ 0 ].start.isEqual( ranges[ 0 ].end ) ).to.be.true;
-			expect( ranges[ 0 ].start.parent ).to.be.instanceof( ViewText ); // "foo".
-			expect( ranges[ 0 ].start.offset ).to.equal( 1 );
-
-			// Change selection attribute, convert it.
-			model.change( writer => {
-				writer.setSelectionAttribute( 'italic', 'i' );
-			} );
-
-			// Check if view structure has changed.
-			expect( viewToString( viewRoot ) ).to.equal( '<div><em>f</em><i></i><em>oo</em></div>' );
-
-			// Check if view selection is inside new <em> element.
-			ranges = Array.from( viewSelection.getRanges() );
-			expect( ranges.length ).to.equal( 1 );
-			expect( ranges[ 0 ].start.isEqual( ranges[ 0 ].end ) ).to.be.true;
-			expect( ranges[ 0 ].start.parent.name ).to.equal( 'i' );
-			expect( ranges[ 0 ].start.offset ).to.equal( 0 );
-
-			// Some more tests checking how selection attributes changes are converted:
-			model.change( writer => {
-				writer.removeSelectionAttribute( 'italic' );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><em>f</em><em>oo</em></div>' );
-			ranges = Array.from( viewSelection.getRanges() );
-			expect( ranges[ 0 ].start.parent.name ).to.equal( 'div' );
-			expect( ranges[ 0 ].start.offset ).to.equal( 1 );
-
-			model.change( writer => {
-				writer.setSelectionAttribute( 'italic', 'em' );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><em>foo</em></div>' );
-			ranges = Array.from( viewSelection.getRanges() );
-			expect( ranges.length ).to.equal( 1 );
-			expect( ranges[ 0 ].start.isEqual( ranges[ 0 ].end ) ).to.be.true;
-			expect( ranges[ 0 ].start.parent ).to.be.instanceof( ViewText ); // "foo".
-			expect( ranges[ 0 ].start.offset ).to.equal( 1 );
-		} );
-	} );
-
-	describe( 'model attribute to view attribute conversion', () => {
-		it( 'using default 1-to-1 conversion', () => {
-			buildModelConverter().for( dispatcher ).fromAttribute( 'class' ).toAttribute();
-
-			const modelElement = new ModelElement( 'paragraph', { class: 'myClass' }, new ModelText( 'foobar' ) );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p class="myClass">foobar</p></div>' );
-
-			model.change( writer => {
-				writer.setAttribute( 'class', 'newClass', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p class="newClass">foobar</p></div>' );
-
-			model.change( writer => {
-				writer.removeAttribute( 'class', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-		} );
-
-		it( 'using passed attribute key', () => {
-			buildModelConverter().for( dispatcher ).fromAttribute( 'theme' ).toAttribute( 'class' );
-
-			const modelElement = new ModelElement( 'paragraph', { theme: 'abc' }, new ModelText( 'foobar' ) );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p class="abc">foobar</p></div>' );
-
-			model.change( writer => {
-				writer.setAttribute( 'theme', 'xyz', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p class="xyz">foobar</p></div>' );
-
-			model.change( writer => {
-				writer.removeAttribute( 'theme', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-		} );
-
-		it( 'using passed attribute key and value', () => {
-			buildModelConverter().for( dispatcher ).fromAttribute( 'highlighted' ).toAttribute( 'style', 'background:yellow' );
-
-			const modelElement = new ModelElement( 'paragraph', { 'highlighted': true }, new ModelText( 'foobar' ) );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p style="background:yellow;">foobar</p></div>' );
-
-			model.change( writer => {
-				writer.removeAttribute( 'highlighted', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-		} );
-
-		it( 'using passed attribute creator function', () => {
-			buildModelConverter().for( dispatcher )
-				.fromAttribute( 'theme' )
-				.toAttribute( value => ( { key: 'class', value: value + '-theme' } ) );
-
-			const modelElement = new ModelElement( 'paragraph', { theme: 'nice' }, new ModelText( 'foobar' ) );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p class="nice-theme">foobar</p></div>' );
-
-			model.change( writer => {
-				writer.setAttribute( 'theme', 'good', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p class="good-theme">foobar</p></div>' );
-
-			model.change( writer => {
-				writer.removeAttribute( 'theme', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-		} );
-	} );
-
-	describe( 'model marker to highlight converter', () => {
-		let modelText, modelElement;
-
-		beforeEach( () => {
-			modelText = new ModelText( 'foobar' );
-			modelElement = new ModelElement( 'paragraph', null, [ modelText ] );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-		} );
-
-		it( 'using passed highlight descriptor object', () => {
-			buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toHighlight( {
-				class: 'highlight',
-				priority: 3,
-				attributes: { title: 'highlight title' }
-			} );
-
-			model.change( writer => {
-				writer.setMarker( 'search', ModelRange.createFromParentsAndOffsets( modelElement, 2, modelElement, 4 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal(
-				'<div>' +
-					'<p>' +
-						'fo' +
-						'<span class="highlight" title="highlight title">ob</span>' +
-						'ar' +
-					'</p>' +
-				'</div>' );
-
-			expect( viewRoot.getChild( 0 ).getChild( 1 ).priority ).to.equal( 3 );
-
-			model.change( writer => {
-				writer.removeMarker( 'search' );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-		} );
-
-		it( 'using passed highlight descriptor object creator', () => {
-			buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toHighlight( () => ( {
-				class: 'highlight',
-				priority: 12,
-				attributes: { title: 'highlight title' }
-			} ) );
-
-			model.change( writer => {
-				writer.setMarker( 'search', ModelRange.createFromParentsAndOffsets( modelElement, 2, modelElement, 4 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal(
-				'<div>' +
-					'<p>' +
-						'fo' +
-						'<span class="highlight" title="highlight title">ob</span>' +
-						'ar' +
-					'</p>' +
-				'</div>' );
-
-			expect( viewRoot.getChild( 0 ).getChild( 1 ).priority ).to.equal( 12 );
-
-			model.change( writer => {
-				writer.removeMarker( 'search' );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-		} );
-
-		it( 'should do nothing when marker range is collapsed', () => {
-			buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toHighlight( {
-				class: 'highlight'
-			} );
-
-			model.change( writer => {
-				writer.setMarker( 'search', ModelRange.createFromParentsAndOffsets( modelElement, 2, modelElement, 2 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-
-			model.change( writer => {
-				writer.removeMarker( 'search' );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-		} );
-
-		it( 'should create converters with provided priority', () => {
-			buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toHighlight( { class: 'highlight' } );
-			buildModelConverter().for( dispatcher ).fromMarker( 'search' ).withPriority( 'high' ).toHighlight( { class: 'override' } );
-
-			model.change( writer => {
-				writer.setMarker( 'search', ModelRange.createFromParentsAndOffsets( modelElement, 2, modelElement, 4 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal(
-				'<div>' +
-					'<p>' +
-						'fo' +
-						'<span class="override">ob</span>' +
-						'ar' +
-					'</p>' +
-				'</div>' );
-		} );
-
-		it( 'should throw if trying to convert from attribute', () => {
-			expect( () => {
-				buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).toHighlight( { class: 'foo' } );
-			} ).to.throw( CKEditorError, /^build-model-converter-non-marker-to-highlight/ );
-		} );
-
-		it( 'should throw if trying to convert from element', () => {
-			expect( () => {
-				buildModelConverter().for( dispatcher ).fromElement( 'paragraph' ).toHighlight( { class: 'foo' } );
-			} ).to.throw( CKEditorError, /^build-model-converter-non-marker-to-highlight/ );
-		} );
-	} );
-
-	describe( 'model marker to view element conversion', () => {
-		let modelText, modelElement, range;
-
-		beforeEach( () => {
-			modelText = new ModelText( 'foobar' );
-			modelElement = new ModelElement( 'paragraph', null, [ modelText ] );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-		} );
-
-		describe( 'collapsed range', () => {
-			beforeEach( () => {
-				range = ModelRange.createFromParentsAndOffsets( modelElement, 2, modelElement, 2 );
-			} );
-
-			it( 'using passed view element name', () => {
-				buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toElement( 'span' );
-
-				model.change( writer => {
-					writer.setMarker( 'search', range );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><p>fo<span></span>obar</p></div>' );
-
-				model.change( writer => {
-					writer.removeMarker( 'search' );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-			} );
-
-			it( 'using passed view element', () => {
-				const viewElement = new ViewUIElement( 'span', { class: 'search' } );
-				buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toElement( viewElement );
-
-				model.change( writer => {
-					writer.setMarker( 'search', range );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><p>fo<span class="search"></span>obar</p></div>' );
-
-				model.change( writer => {
-					writer.removeMarker( 'search' );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-			} );
-
-			it( 'using passed creator function', () => {
-				buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toElement( data => {
-					const className = 'search search-color-' + data.markerName.split( ':' )[ 1 ];
-
-					return new ViewUIElement( 'span', { class: className } );
-				} );
-
-				model.change( writer => {
-					writer.setMarker( 'search:red', range );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><p>fo<span class="search search-color-red"></span>obar</p></div>' );
-
-				model.change( writer => {
-					writer.removeMarker( 'search:red' );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-			} );
-		} );
-
-		describe( 'non-collapsed range', () => {
-			beforeEach( () => {
-				range = ModelRange.createFromParentsAndOffsets( modelElement, 2, modelElement, 4 );
-			} );
-
-			it( 'using passed view element name', () => {
-				buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toElement( 'span' );
-
-				model.change( writer => {
-					writer.setMarker( 'search', range );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><p>fo<span></span>ob<span></span>ar</p></div>' );
-
-				model.change( writer => {
-					writer.removeMarker( 'search' );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-			} );
-
-			it( 'using passed view element', () => {
-				const viewElement = new ViewUIElement( 'span', { class: 'search' } );
-				buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toElement( viewElement );
-
-				model.change( writer => {
-					writer.setMarker( 'search', range );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal(
-					'<div><p>fo<span class="search"></span>ob<span class="search"></span>ar</p></div>'
-				);
-
-				model.change( writer => {
-					writer.removeMarker( 'search' );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-			} );
-
-			it( 'using passed creator function', () => {
-				buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toElement( data => {
-					const className = 'search search-color-' + data.markerName.split( ':' )[ 1 ];
-
-					return new ViewUIElement( 'span', { class: className } );
-				} );
-
-				model.change( writer => {
-					writer.setMarker( 'search:red', range );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal(
-					'<div><p>fo<span class="search search-color-red"></span>ob<span class="search search-color-red"></span>ar</p></div>'
-				);
-
-				model.change( writer => {
-					writer.removeMarker( 'search:red' );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
-			} );
-		} );
-
-		it( 'should overwrite default priority', () => {
-			range = ModelRange.createFromParentsAndOffsets( modelElement, 2, modelElement, 2 );
-
-			buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toElement( 'normal' );
-			buildModelConverter().for( dispatcher ).fromMarker( 'search' ).withPriority( 'high' ).toElement( 'high' );
-
-			model.change( writer => {
-				writer.setMarker( 'search', range );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>fo<high></high>obar</p></div>' );
-		} );
-
-		it( 'should throw when trying to build model element to view attribute converter', () => {
-			expect( () => {
-				buildModelConverter().for( dispatcher ).fromElement( 'paragraph' ).toAttribute( 'paragraph', true );
-			} ).to.throw( CKEditorError, /^build-model-converter-non-attribute-to-attribute/ );
-		} );
-	} );
-} );

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

@@ -1,631 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import buildViewConverter from '../../src/conversion/buildviewconverter';
-
-import Model from '../../src/model/model';
-import ModelDocumentFragment from '../../src/model/documentfragment';
-import ModelElement from '../../src/model/element';
-import ModelTextProxy from '../../src/model/textproxy';
-import ModelRange from '../../src/model/range';
-import ModelWalker from '../../src/model/treewalker';
-
-import ViewDocumentFragment from '../../src/view/documentfragment';
-import ViewContainerElement from '../../src/view/containerelement';
-import ViewAttributeElement from '../../src/view/attributeelement';
-import ViewText from '../../src/view/text';
-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 ) {
-	let result = '';
-
-	for ( const attr of item.getAttributes() ) {
-		result += ' ' + attr[ 0 ] + '="' + attr[ 1 ] + '"';
-	}
-
-	return result;
-}
-
-function modelToString( item ) {
-	let result = '';
-
-	if ( item instanceof ModelTextProxy ) {
-		const attributes = modelAttributesToString( item );
-
-		result = attributes ? '<$text' + attributes + '>' + item.data + '</$text>' : item.data;
-	} else {
-		const walker = new ModelWalker( { boundaries: ModelRange.createIn( item ), shallow: true } );
-
-		for ( const value of walker ) {
-			result += modelToString( value.item );
-		}
-
-		if ( item instanceof ModelElement ) {
-			const attributes = modelAttributesToString( item );
-
-			result = '<' + item.name + attributes + '>' + result + '</' + item.name + '>';
-		}
-	}
-
-	return result;
-}
-
-const textAttributes = [ 'linkHref', 'linkTitle', 'bold', 'italic', 'style' ];
-const pAttributes = [ 'class', 'important', 'theme', 'decorated', 'size' ];
-
-describe( 'View converter builder', () => {
-	let dispatcher, model, schema, context;
-
-	beforeEach( () => {
-		model = new Model();
-
-		// `context` parameter for `.convert` calls.
-		context = [ '$root' ];
-
-		schema = model.schema;
-
-		schema.register( 'paragraph', {
-			inheritAllFrom: '$block',
-			allowAttributes: pAttributes
-		} );
-		schema.register( 'div', {
-			inheritAllFrom: '$block',
-			allowAttributes: 'class'
-		} );
-		schema.register( 'customP', {
-			inheritAllFrom: 'paragraph'
-		} );
-		schema.register( 'image', {
-			inheritAllFrom: '$text',
-			allowAttributes: 'src'
-		} );
-		schema.register( 'span', {
-			inheritAllFrom: '$text',
-			allowAttributes: 'transformer'
-		} );
-		// Yes, folks, we are building MEGATRON.
-		schema.register( 'MEGATRON', {
-			inheritAllFrom: '$text'
-		} );
-		schema.register( 'abcd', {
-			inheritAllFrom: '$text'
-		} );
-		schema.extend( '$text', {
-			allowAttributes: textAttributes,
-			allowIn: [ '$root', 'span', 'abcd', 'MEGATRON' ]
-		} );
-
-		dispatcher = new ViewConversionDispatcher( model, { schema } );
-		dispatcher.on( 'text', convertText() );
-	} );
-
-	it( 'should convert from view element to model element', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-
-		const conversionResult = dispatcher.convert( new ViewContainerElement( 'p', null, new ViewText( 'foo' ) ) );
-
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph>foo</paragraph>' );
-	} );
-
-	it( 'should convert from view element to model element using creator function', () => {
-		buildViewConverter().for( dispatcher )
-			.fromElement( 'img' )
-			.toElement( viewElement => new ModelElement( 'image', { src: viewElement.getAttribute( 'src' ) } ) );
-
-		const conversionResult = dispatcher.convert( new ViewContainerElement( 'img', { src: 'foo.jpg' } ), context );
-
-		expect( modelToString( conversionResult ) ).to.equal( '<image src="foo.jpg"></image>' );
-	} );
-
-	it( 'should convert to model element when element children are not allowed in parent (empty split elements should be removed)', () => {
-		schema.register( 'section', {
-			inheritAllFrom: '$block'
-		} );
-
-		buildViewConverter().for( dispatcher )
-			.fromElement( 'p' )
-			.toElement( 'paragraph' );
-
-		buildViewConverter().for( dispatcher )
-			.fromElement( 'section' )
-			.toElement( 'section' );
-
-		const input = new ViewContainerElement( 'p', null, [
-			new ViewText( 'foo' ),
-			new ViewContainerElement( 'section', null, [
-				new ViewContainerElement( 'p', null, [
-					new ViewText( 'abc' ),
-					new ViewContainerElement( 'section' ),
-					new ViewText( 'cde' ),
-				] )
-			] ),
-			new ViewText( 'bar' ),
-		] );
-
-		const conversionResult = dispatcher.convert( input );
-
-		expect( modelToString( conversionResult ) ).to.equal(
-			'<paragraph>foo</paragraph>' +
-			'<paragraph>abc</paragraph>' +
-			'<section></section>' +
-			'<paragraph>cde</paragraph>' +
-			'<paragraph>bar</paragraph>'
-		);
-	} );
-
-	it( 'should convert from view element to model attribute', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'strong' ).toAttribute( 'bold', true );
-
-		const conversionResult = dispatcher.convert(
-			new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), context
-		);
-
-		// Have to check root because result is a ModelText.
-		expect( modelToString( conversionResult ) ).to.equal( '<$text bold="true">foo</$text>' );
-	} );
-
-	it( 'should convert from view element to model attributes using creator function', () => {
-		buildViewConverter().for( dispatcher )
-			.fromElement( 'a' )
-			.toAttribute( viewElement => ( { key: 'linkHref', value: viewElement.getAttribute( 'href' ) } ) );
-
-		const conversionResult = dispatcher.convert(
-			new ViewAttributeElement( 'a', { href: 'foo.html' }, new ViewText( 'foo' ) ), context
-		);
-
-		// Have to check root because result is a ModelText.
-		expect( modelToString( conversionResult ) ).to.equal( '<$text linkHref="foo.html">foo</$text>' );
-	} );
-
-	it( 'should convert from view attribute to model attribute', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-
-		buildViewConverter().for( dispatcher )
-			.fromAttribute( 'class' )
-			.toAttribute( viewElement => ( { key: 'class', value: viewElement.getAttribute( 'class' ) } ) );
-
-		const conversionResult = dispatcher.convert(
-			new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), context
-		);
-
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph class="myClass">foo</paragraph>' );
-	} );
-
-	it( 'should convert from view attribute and key to model attribute', () => {
-		schema.extend( 'paragraph', { allowAttributes: 'type' } );
-
-		dispatcher.on( 'documentFragment', convertToModelFragment() );
-
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-		buildViewConverter().for( dispatcher ).fromAttribute( 'class', 'important' ).toAttribute( 'important', true );
-		buildViewConverter().for( dispatcher ).fromAttribute( 'class', 'theme-nice' ).toAttribute( 'theme', 'nice' );
-		buildViewConverter().for( dispatcher ).fromAttribute( 'data-type' ).toAttribute( 'type' );
-
-		const viewStructure = new ViewDocumentFragment( [
-			new ViewContainerElement( 'p', { class: 'important' }, new ViewText( 'foo' ) ),
-			new ViewContainerElement( 'p', { class: 'important theme-nice' }, new ViewText( 'bar' ) ),
-			new ViewContainerElement( 'p', { 'data-type': 'foo' }, new ViewText( 'xyz' ) )
-		] );
-
-		const conversionResult = dispatcher.convert( viewStructure, context );
-
-		expect( modelToString( conversionResult ) ).to.equal(
-			'<paragraph important="true">foo</paragraph>' +
-			'<paragraph important="true" theme="nice">bar</paragraph>' +
-			'<paragraph type="foo">xyz</paragraph>'
-		);
-	} );
-
-	it( 'should convert from multiple view entities to model attribute', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-
-		buildViewConverter().for( dispatcher )
-			.fromElement( 'strong' )
-			.fromElement( 'b' )
-			.fromAttribute( 'class', 'bold' )
-			.fromAttribute( 'style', { 'font-weight': 'bold' } )
-			.toAttribute( 'bold', true );
-
-		const viewElement = new ViewContainerElement( 'p', null, [
-			new ViewAttributeElement( 'strong', null, new ViewText( 'aaa' ) ),
-			new ViewAttributeElement( 'b', null, new ViewText( 'bbb' ) ),
-			new ViewContainerElement( 'span', { class: 'bold' }, new ViewText( 'ccc' ) ),
-			new ViewContainerElement( 'span', { style: 'font-weight:bold; font-size:20px' }, new ViewText( 'ddd' ) )
-		] );
-
-		const conversionResult = dispatcher.convert( viewElement, context );
-
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph><$text bold="true">aaabbbcccddd</$text></paragraph>' );
-	} );
-
-	it( 'should convert from pattern to marker', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-		buildViewConverter().for( dispatcher ).from( { attribute: { 'data-name': 'search' } } ).toMarker();
-
-		const viewElement = new ViewContainerElement( 'p', null, [
-			new ViewText( 'Fo' ),
-			new ViewAttributeElement( 'marker', { 'data-name': 'search' } ),
-			new ViewText( 'o ba' ),
-			new ViewAttributeElement( 'marker', { 'data-name': 'search' } ),
-			new ViewText( 'r' )
-		] );
-
-		const conversionResult = dispatcher.convert( viewElement, context );
-
-		const markerSearch = conversionResult.markers.get( 'search' );
-
-		expect( conversionResult.markers.size ).to.equal( 1 );
-		expect( markerSearch.start.path ).to.deep.equal( [ 0, 2 ] );
-		expect( markerSearch.end.path ).to.deep.equal( [ 0, 6 ] );
-	} );
-
-	it( 'should convert from element to marker using creator function', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-		buildViewConverter().for( dispatcher ).fromElement( 'marker' ).toMarker( data => {
-			return new ModelElement( '$marker', { 'data-name': data.getAttribute( 'class' ) } );
-		} );
-
-		const viewElement = new ViewContainerElement( 'p', null, [
-			new ViewText( 'Fo' ),
-			new ViewAttributeElement( 'marker', { 'class': 'search' } ),
-			new ViewText( 'o ba' ),
-			new ViewAttributeElement( 'marker', { 'class': 'search' } ),
-			new ViewText( 'r' )
-		] );
-
-		const conversionResult = dispatcher.convert( viewElement, context );
-
-		const markerSearch = conversionResult.markers.get( 'search' );
-
-		expect( conversionResult.markers.size ).to.equal( 1 );
-		expect( markerSearch.start.path ).to.deep.equal( [ 0, 2 ] );
-		expect( markerSearch.end.path ).to.deep.equal( [ 0, 6 ] );
-	} );
-
-	it( 'should convert from multiple view entities to marker', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-		buildViewConverter().for( dispatcher )
-			.from( { attribute: { 'foo': 'marker' } } )
-			.from( { attribute: { 'bar': 'marker' } } )
-			.from( { attribute: { 'foo': 'marker', 'bar': 'marker' } } )
-			.toMarker();
-
-		const viewElement = new ViewContainerElement( 'p', null, [
-			new ViewText( 'Fo' ),
-			new ViewAttributeElement( 'span', { 'foo': 'marker', 'data-name': 'marker1' } ),
-			new ViewText( 'o b' ),
-			new ViewAttributeElement( 'span', { 'bar': 'marker', 'data-name': 'marker2' } ),
-			new ViewText( 'a' ),
-			new ViewAttributeElement( 'span', { 'foo': 'marker', 'bar': 'marker', 'data-name': 'marker3' } ),
-			new ViewText( 'r' )
-		] );
-
-		const conversionResult = dispatcher.convert( viewElement );
-
-		const marker1 = conversionResult.markers.get( 'marker1' );
-		const marker2 = conversionResult.markers.get( 'marker2' );
-		const marker3 = conversionResult.markers.get( 'marker3' );
-
-		expect( conversionResult.markers.size ).to.equal( 3 );
-		expect( marker1.start.path ).to.deep.equal( marker1.end.path ).to.deep.equal( [ 0, 2 ] );
-		expect( marker2.start.path ).to.deep.equal( marker2.end.path ).to.deep.equal( [ 0, 5 ] );
-		expect( marker3.start.path ).to.deep.equal( marker3.end.path ).to.deep.equal( [ 0, 6 ] );
-	} );
-
-	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' );
-
-		const result = dispatcher.convert( element );
-
-		expect( result ).to.be.instanceof( ModelDocumentFragment );
-		expect( result.childCount ).to.equal( 0 );
-	} );
-
-	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, context );
-		} ).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', { 'data-name': 'search' } );
-		} );
-
-		const element = new ViewAttributeElement( 'marker', { 'data-name': 'search' } );
-
-		expect( () => {
-			dispatcher.convert( element, context );
-		} ).to.throw( CKEditorError, /^build-view-converter-invalid-marker/ );
-	} );
-
-	it( 'should throw an error when model element returned by creator has not valid data-name attribute', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'marker' ).toMarker( () => {
-			return new ModelElement( '$marker', { 'foo': 'search' } );
-		} );
-
-		const element = new ViewAttributeElement( 'marker', { 'data-name': 'search' } );
-
-		expect( () => {
-			dispatcher.convert( element, context );
-		} ).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' } }
-		).toElement( 'MEGATRON' );
-
-		// Adding callbacks later so they are called later. MEGATRON callback is more important.
-		buildViewConverter().for( dispatcher ).fromElement( 'span' ).toElement( 'span' );
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-
-		let result;
-
-		// Not quite megatron.
-		result = dispatcher.convert(
-			new ViewContainerElement( 'span', { class: 'megatron' }, new ViewText( 'foo' ) ), context
-		);
-
-		expect( modelToString( result ) ).to.equal( '<span>foo</span>' );
-
-		// Almost a megatron. Missing a head.
-		result = dispatcher.convert(
-			new ViewContainerElement( 'span', { class: 'megatron', body: 'megatron', legs: 'megatron' }, new ViewText( 'foo' ) ),
-			context
-		);
-
-		expect( modelToString( result ) ).to.equal( '<span>foo</span>' );
-
-		// This would be a megatron but is a paragraph.
-		result = dispatcher.convert(
-			new ViewContainerElement(
-				'p',
-				{ class: 'megatron', body: 'megatron', legs: 'megatron', head: 'megatron' },
-				new ViewText( 'foo' )
-			),
-			context
-		);
-
-		expect( modelToString( result ) ).to.equal( '<paragraph>foo</paragraph>' );
-
-		// At last we have a megatron!
-		result = dispatcher.convert(
-			new ViewContainerElement(
-				'span',
-				{ class: 'megatron', body: 'megatron', legs: 'megatron', head: 'megatron' },
-				new ViewText( 'foo' )
-			),
-			context
-		);
-
-		expect( modelToString( result ) ).to.equal( '<MEGATRON>foo</MEGATRON>' );
-	} );
-
-	it( 'should convert from pattern to model attribute', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'span' ).toElement( 'span' );
-
-		// This time without name so default span converter will convert children.
-		buildViewConverter().for( dispatcher )
-			.from( { class: 'megatron', attribute: { head: 'megatron', body: 'megatron', legs: 'megatron' } } )
-			.toAttribute( 'transformer', 'megatron' );
-
-		const viewElement = new ViewContainerElement(
-			'span',
-			{ class: 'megatron', body: 'megatron', legs: 'megatron', head: 'megatron' },
-			new ViewText( 'foo' )
-		);
-
-		const conversionResult = dispatcher.convert( viewElement, context );
-
-		expect( modelToString( conversionResult ) ).to.equal( '<span transformer="megatron">foo</span>' );
-	} );
-
-	it( 'should return model document fragment when converting attributes on text', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'strong' ).toAttribute( 'bold', true );
-
-		const viewElement = new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) );
-
-		const conversionResult = dispatcher.convert( viewElement, context );
-
-		expect( conversionResult.is( 'documentFragment' ) ).to.be.true;
-	} );
-
-	it( 'should set different priorities for `toElement` and `toAttribute` conversion', () => {
-		buildViewConverter().for( dispatcher )
-			.fromAttribute( 'class' )
-			.toAttribute( viewElement => ( { key: 'class', value: viewElement.getAttribute( 'class' ) } ) );
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-
-		const conversionResult = dispatcher.convert(
-			new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), context
-		);
-
-		// Element converter was fired first even though attribute converter was added first.
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph class="myClass">foo</paragraph>' );
-	} );
-
-	it( 'should overwrite default priorities for converters', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-		buildViewConverter().for( dispatcher )
-			.fromAttribute( 'class' )
-			.toAttribute( viewElement => ( { key: 'class', value: viewElement.getAttribute( 'class' ) } ) );
-
-		let result;
-
-		result = dispatcher.convert(
-			new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), context
-		);
-
-		expect( modelToString( result ) ).to.equal( '<paragraph class="myClass">foo</paragraph>' );
-
-		buildViewConverter().for( dispatcher )
-			.from( { name: 'p', class: 'myClass' } ).withPriority( 'high' )
-			.toElement( 'customP' );
-
-		result = dispatcher.convert(
-			new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), context
-		);
-
-		expect( modelToString( result ) ).to.equal( '<customP>foo</customP>' );
-	} );
-
-	it( 'should overwrite default consumed values', () => {
-		// Converter (1).
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-
-		// Converter (2).
-		buildViewConverter().for( dispatcher )
-			.from( { name: 'p', class: 'decorated' } ).consuming( { class: 'decorated' } )
-			.toAttribute( 'decorated', true );
-
-		// Converter (3).
-		buildViewConverter().for( dispatcher )
-			.fromAttribute( 'class', 'small' ).consuming( { class: 'small' } )
-			.toAttribute( 'size', 'small' );
-
-		const viewElement = new ViewContainerElement( 'p', { class: 'decorated small' }, new ViewText( 'foo' ) );
-		const conversionResult = dispatcher.convert( viewElement, context );
-
-		// P element and it's children got converted by the converter (1) and the converter (1) got fired
-		// because P name was not consumed in converter (2). Converter (3) could consume class="small" because
-		// only class="decorated" was consumed in converter (2).
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph decorated="true" size="small">foo</paragraph>' );
-	} );
-
-	it( 'should convert from matcher instance to model', () => {
-		// Universal class converter, synonymous to .fromAttribute( 'class' ).
-		buildViewConverter().for( dispatcher )
-			.from( new ViewMatcher( { class: /.*/ } ) )
-			.toAttribute( viewElement => ( { key: 'class', value: viewElement.getAttribute( 'class' ) } ) );
-
-		// Universal element converter.
-		buildViewConverter().for( dispatcher )
-			.from( new ViewMatcher( { name: /.*/ } ) )
-			.toElement( viewElement => new ModelElement( viewElement.name ) );
-
-		const viewStructure = new ViewContainerElement( 'div', { class: 'myClass' }, [
-			new ViewContainerElement( 'abcd', null, new ViewText( 'foo' ) )
-		] );
-
-		const conversionResult = dispatcher.convert( viewStructure, context );
-
-		expect( modelToString( conversionResult ) ).to.equal( '<div class="myClass"><abcd>foo</abcd></div>' );
-	} );
-
-	it( 'should filter out structure that is wrong with schema - elements', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'div' ).toElement( 'div' );
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-
-		// Disallow $root>div.
-		schema.addChildCheck( ( ctx, childDef ) => {
-			if ( childDef.name == 'div' && ctx.endsWith( '$root' ) ) {
-				return false;
-			}
-		} );
-
-		dispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
-
-		const viewElement = new ViewContainerElement( 'div', null,
-			new ViewContainerElement( 'p', null,
-				new ViewText( 'foo' )
-			)
-		);
-
-		const conversionResult = dispatcher.convert( viewElement, context );
-
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph>foo</paragraph>' );
-	} );
-
-	it( 'should filter out structure that is wrong with schema - attributes', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-		buildViewConverter().for( dispatcher ).fromElement( 'strong' ).toAttribute( 'bold', true );
-
-		// Disallow bold in paragraph>$text.
-		schema.addAttributeCheck( ( ctx, attributeName ) => {
-			if ( ctx.endsWith( 'paragraph $text' ) && attributeName == 'bold' ) {
-				return false;
-			}
-		} );
-
-		dispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
-
-		const viewElement = new ViewContainerElement( 'p', null,
-			new ViewAttributeElement( 'strong', null,
-				new ViewText( 'foo' )
-			)
-		);
-
-		const conversionResult = dispatcher.convert( viewElement, context );
-
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph>foo</paragraph>' );
-	} );
-
-	it( 'should not set attribute when it is not allowed', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-		buildViewConverter().for( dispatcher ).fromElement( 'u' ).toAttribute( 'underscore', true );
-
-		const viewElement = new ViewContainerElement( 'p', null,
-			new ViewAttributeElement( 'u', null,
-				new ViewText( 'foo' )
-			)
-		);
-
-		const conversionResult = dispatcher.convert( viewElement, context );
-
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph>foo</paragraph>' );
-	} );
-
-	it( 'should stop to element conversion if creating function returned null', () => {
-		buildViewConverter()
-			.for( dispatcher )
-			.fromElement( 'p' )
-			.toElement( viewElement => {
-				return viewElement.hasAttribute( 'stop' ) ? null : new ModelElement( 'paragraph' );
-			} );
-
-		const viewElement = new ViewContainerElement( 'p' );
-		let conversionResult = dispatcher.convert( viewElement, context );
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph></paragraph>' );
-
-		viewElement.setAttribute( 'stop', true );
-		conversionResult = dispatcher.convert( viewElement, context );
-
-		expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
-		expect( conversionResult.childCount ).to.equal( 0 );
-	} );
-
-	it( 'should stop to attribute conversion if creating function returned null', () => {
-		schema.extend( 'paragraph', { allowAttributes: 'type' } );
-
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-
-		buildViewConverter().for( dispatcher ).fromAttribute( 'data-type' ).toAttribute( viewElement => {
-			const value = viewElement.getAttribute( 'data-type' );
-
-			return value == 'stop' ? null : { key: 'type', value };
-		} );
-
-		const viewElement = new ViewContainerElement( 'p', { 'data-type': 'foo' } );
-		let conversionResult = dispatcher.convert( viewElement, context );
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph type="foo"></paragraph>' );
-
-		viewElement.setAttribute( 'data-type', 'stop' );
-		conversionResult = dispatcher.convert( viewElement, context );
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph></paragraph>' );
-	} );
-} );

+ 0 - 484
packages/ckeditor5-engine/tests/conversion/definition-based-converters.js

@@ -1,484 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import ModelElement from '../../src/model/element';
-import ModelText from '../../src/model/text';
-import ModelRange from '../../src/model/range';
-
-import ViewElement from '../../src/view/element';
-import ViewAttributeElement from '../../src/view/attributeelement';
-import ViewText from '../../src/view/text';
-
-import { convertText } from '../../src/conversion/view-to-model-converters';
-
-import {
-	modelAttributeToViewAttributeElement,
-	viewToModelAttribute,
-	modelElementToViewContainerElement,
-	viewToModelElement
-} from '../../src/conversion/definition-based-converters';
-
-import ViewConversionDispatcher from '../../src/conversion/viewconversiondispatcher';
-import ModelWalker from '../../src/model/treewalker';
-import ModelTextProxy from '../../src/model/textproxy';
-import Model from '../../src/model/model';
-import ModelPosition from '../../src/model/position';
-import EditingController from '../../src/controller/editingcontroller';
-
-function viewAttributesToString( item ) {
-	let result = '';
-
-	for ( const key of item.getAttributeKeys() ) {
-		const value = item.getAttribute( key );
-
-		if ( value ) {
-			result += ' ' + key + '="' + value + '"';
-		}
-	}
-
-	return result;
-}
-
-function modelToString( item ) {
-	let result = '';
-
-	if ( item instanceof ModelTextProxy ) {
-		const attributes = modelAttributesToString( item );
-
-		result = attributes ? '<$text' + attributes + '>' + item.data + '</$text>' : item.data;
-	} else {
-		const walker = new ModelWalker( { boundaries: ModelRange.createIn( item ), shallow: true } );
-
-		for ( const value of walker ) {
-			result += modelToString( value.item );
-		}
-
-		if ( item instanceof ModelElement ) {
-			const attributes = modelAttributesToString( item );
-
-			result = '<' + item.name + attributes + '>' + result + '</' + item.name + '>';
-		}
-	}
-
-	return result;
-}
-
-function modelAttributesToString( item ) {
-	let result = '';
-
-	for ( const attr of item.getAttributes() ) {
-		result += ' ' + attr[ 0 ] + '="' + attr[ 1 ] + '"';
-	}
-
-	return result;
-}
-
-function viewToString( item ) {
-	let result = '';
-
-	if ( item instanceof ViewText ) {
-		result = item.data;
-	} else {
-		// ViewElement or ViewDocumentFragment.
-		for ( const child of item.getChildren() ) {
-			result += viewToString( child );
-		}
-
-		if ( item instanceof ViewElement ) {
-			result = '<' + item.name + viewAttributesToString( item ) + '>' + result + '</' + item.name + '>';
-		}
-	}
-
-	return result;
-}
-
-describe( 'definition-based-converters', () => {
-	let model, dispatcher, modelDoc, modelRoot, viewRoot, controller, context, schema;
-
-	beforeEach( () => {
-		model = new Model();
-	} );
-
-	function setupViewToModelTests() {
-		context = [ '$root' ];
-		schema = model.schema;
-		dispatcher = new ViewConversionDispatcher( model, { schema } );
-	}
-
-	function setupModelToViewTests() {
-		modelDoc = model.document;
-		modelRoot = modelDoc.createRoot();
-
-		controller = new EditingController( model );
-
-		// Set name of view root the same as dom root.
-		// This is a mock of attaching view root to dom root.
-		controller.view.getRoot()._name = 'div';
-
-		viewRoot = controller.view.getRoot();
-		dispatcher = controller.modelToView;
-	}
-
-	describe( 'Attribute converters', () => {
-		function testModelConversion( definition, expectedConversion ) {
-			modelAttributeToViewAttributeElement( 'foo', [ definition ], [ dispatcher ] );
-
-			const modelElement = new ModelText( 'foo', { foo: 'bar' } );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( expectedConversion );
-
-			model.change( writer => {
-				writer.removeAttribute( 'foo', modelElement );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div>foo</div>' );
-		}
-
-		describe( 'model to view conversion', () => {
-			beforeEach( () => {
-				setupModelToViewTests();
-			} );
-
-			it( 'using passed view element name', () => {
-				testModelConversion( { model: 'bar', view: 'strong' }, '<div><strong>foo</strong></div>' );
-			} );
-
-			it( 'using passed view element object', () => {
-				testModelConversion( { model: 'bar', view: { name: 'strong' } }, '<div><strong>foo</strong></div>' );
-			} );
-
-			it( 'using passed view element object with style object', () => {
-				testModelConversion( {
-					model: 'bar',
-					view: { name: 'span', style: { 'font-weight': 'bold' } }
-				}, '<div><span style="font-weight:bold;">foo</span></div>' );
-			} );
-
-			it( 'using passed view element object with class string', () => {
-				testModelConversion( { model: 'bar', view: { name: 'span', class: 'foo' } }, '<div><span class="foo">foo</span></div>' );
-			} );
-
-			it( 'using passed view element object with class array', () => {
-				testModelConversion( {
-					model: 'bar',
-					view: { name: 'span', class: [ 'foo', 'foo-bar' ] }
-				}, '<div><span class="foo foo-bar">foo</span></div>' );
-			} );
-
-			it( 'using passed view element object with attributes', () => {
-				testModelConversion( {
-					model: 'bar',
-					view: { name: 'span', attribute: { 'data-foo': 'bar' } }
-				}, '<div><span data-foo="bar">foo</span></div>' );
-			} );
-
-			it( 'should convert when changing attribute', () => {
-				const definition1 = { model: 'bar', view: { name: 'span', class: 'bar' } };
-				const definition2 = { model: 'baz', view: { name: 'span', class: 'baz' } };
-
-				modelAttributeToViewAttributeElement( 'foo', [ definition1, definition2 ], [ dispatcher ] );
-
-				const modelElement = new ModelText( 'foo', { foo: 'bar' } );
-
-				model.change( writer => {
-					writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><span class="bar">foo</span></div>' );
-
-				model.change( writer => {
-					writer.setAttribute( 'foo', 'baz', modelElement );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div><span class="baz">foo</span></div>' );
-			} );
-
-			it( 'should do nothing for undefined value', () => {
-				modelAttributeToViewAttributeElement( 'foo', [ { model: 'bar', view: 'strong' } ], [ dispatcher ] );
-
-				const modelElement = new ModelText( 'foo', { foo: 'baz' } );
-
-				model.change( writer => {
-					writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-				} );
-
-				expect( viewToString( viewRoot ) ).to.equal( '<div>foo</div>' );
-			} );
-		} );
-
-		describe( 'view to model conversion', () => {
-			beforeEach( () => {
-				setupViewToModelTests();
-
-				schema.register( 'div', { inheritAllFrom: '$block' } );
-				schema.extend( '$text', {
-					allowIn: '$root',
-					allowAttributes: 'foo'
-				} );
-
-				dispatcher.on( 'text', convertText() );
-			} );
-
-			it( 'should convert using element name', () => {
-				viewToModelAttribute( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo</$text>' );
-			} );
-
-			it( 'should convert using object', () => {
-				viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo</$text>' );
-			} );
-
-			it( 'should convert using class string', () => {
-				viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', class: 'foo' } }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewAttributeElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo</$text>' );
-			} );
-
-			it( 'should convert using class array', () => {
-				viewToModelAttribute( 'foo', {
-					model: 'bar',
-					view: { name: 'span', class: [ 'foo', 'bar' ] }
-				}, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewAttributeElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo</$text>' );
-			} );
-
-			it( 'should convert using style object', () => {
-				viewToModelAttribute( 'foo', {
-					model: 'bar',
-					view: { name: 'span', style: { 'font-weight': 'bold' } }
-				}, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewAttributeElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo</$text>' );
-			} );
-
-			it( 'should convert using attributes object', () => {
-				viewToModelAttribute( 'foo', {
-					model: 'bar',
-					view: { name: 'span', attribute: { 'data-foo': 'bar' } }
-				}, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo</$text>' );
-			} );
-
-			it( 'should convert using acceptAlso array', () => {
-				viewToModelAttribute( 'foo', {
-					model: 'bar',
-					view: 'strong',
-					acceptsAlso: [
-						{ name: 'span', class: [ 'foo', 'bar' ] },
-						{ name: 'span', attribute: { 'data-foo': 'bar' } }
-					]
-				}, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo</$text>' );
-			} );
-
-			it( 'should convert using priority', () => {
-				viewToModelAttribute( 'foo', { model: 'baz', view: 'strong' }, [ dispatcher ] );
-				viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo</$text>' );
-			} );
-		} );
-	} );
-
-	describe( 'Element converters', () => {
-		function testModelConversion( definition, expectedResult ) {
-			modelElementToViewContainerElement( definition, [ dispatcher ] );
-
-			const modelElement = new ModelElement( 'foo', null, new ModelText( 'bar' ) );
-
-			model.change( writer => {
-				writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div>' + expectedResult + '</div>' );
-		}
-
-		describe( 'model to view conversion', () => {
-			beforeEach( () => {
-				setupModelToViewTests();
-			} );
-
-			it( 'using passed view element name', () => {
-				testModelConversion( { model: 'foo', view: 'code' }, '<code>bar</code>' );
-			} );
-
-			it( 'using passed view element object', () => {
-				testModelConversion( { model: 'foo', view: { name: 'code' } }, '<code>bar</code>' );
-			} );
-
-			it( 'using passed view element object with style object', () => {
-				testModelConversion( {
-					model: 'foo',
-					view: { name: 'span', style: { 'font-weight': 'bold' } }
-				}, '<span style="font-weight:bold;">bar</span>' );
-			} );
-
-			it( 'using passed view element object with class string', () => {
-				testModelConversion( { model: 'foo', view: { name: 'span', class: 'foo' } }, '<span class="foo">bar</span>' );
-			} );
-
-			it( 'using passed view element object with class array', () => {
-				testModelConversion( {
-					model: 'foo',
-					view: { name: 'span', class: [ 'foo', 'foo-bar' ] }
-				}, '<span class="foo foo-bar">bar</span>' );
-			} );
-
-			it( 'using passed view element object with attributes', () => {
-				testModelConversion( {
-					model: 'foo',
-					view: { name: 'span', attribute: { 'data-foo': 'bar' } }
-				}, '<span data-foo="bar">bar</span>' );
-			} );
-		} );
-
-		describe( 'view to model conversion', () => {
-			beforeEach( () => {
-				setupViewToModelTests();
-
-				schema.register( 'div', { inheritAllFrom: '$block' } );
-				schema.register( 'bar', { inheritAllFrom: '$block' } );
-				schema.register( 'baz', { inheritAllFrom: '$block' } );
-
-				schema.extend( '$text', {
-					allowIn: '$root',
-					allowAttributes: 'foo'
-				} );
-
-				dispatcher.on( 'text', convertText() );
-			} );
-
-			it( 'should convert using element name', () => {
-				viewToModelElement( { model: 'bar', view: 'strong' }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewElement( 'strong', null, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
-			} );
-
-			it( 'should convert using object', () => {
-				viewToModelElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewElement( 'strong', null, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
-			} );
-
-			it( 'should convert using class string', () => {
-				viewToModelElement( { model: 'bar', view: { name: 'span', class: 'foo' } }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
-			} );
-
-			it( 'should convert using class array', () => {
-				viewToModelElement( { model: 'bar', view: { name: 'span', class: [ 'foo', 'bar' ] } }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
-			} );
-
-			it( 'should convert using style object', () => {
-				viewToModelElement( { model: 'bar', view: { name: 'span', style: { 'font-weight': 'bold' } } }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
-			} );
-
-			it( 'should convert using attributes object', () => {
-				viewToModelElement( { model: 'bar', view: { name: 'span', attribute: { 'data-foo': 'bar' } } }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
-			} );
-
-			it( 'should convert using acceptAlso array', () => {
-				viewToModelElement( {
-					model: 'bar',
-					view: 'strong',
-					acceptsAlso: [
-						{ name: 'span', class: [ 'foo', 'bar' ] },
-						{ name: 'span', attribute: { 'data-foo': 'bar' } }
-					]
-				}, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
-			} );
-
-			it( 'should convert using priority', () => {
-				viewToModelElement( { model: 'baz', view: 'strong' }, [ dispatcher ] );
-				viewToModelElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] );
-
-				const conversionResult = dispatcher.convert(
-					new ViewElement( 'strong', null, new ViewText( 'foo' ) ), context
-				);
-
-				expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
-			} );
-		} );
-	} );
-} );

+ 28 - 20
packages/ckeditor5-engine/tests/manual/highlight.js

@@ -5,6 +5,19 @@
 
 /* global console, window, document */
 
+import ModelRange from '../../src/model/range';
+import ViewContainerElement from '../../src/view/containerelement';
+import ViewText from '../../src/view/text';
+
+import {
+	elementToElement as vtmElementToElement,
+} from '../../src/conversion/view-to-model-helpers';
+
+import {
+	elementToElement as mtvElementToElement,
+	markerToHighlight as mtvMarkerToHighlight
+} from '../../src/conversion/model-to-view-helpers';
+
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import Enter from '@ckeditor/ckeditor5-enter/src/enter';
 import Typing from '@ckeditor/ckeditor5-typing/src/typing';
@@ -15,12 +28,6 @@ import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
 import List from '@ckeditor/ckeditor5-list/src/list';
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
-import buildModelConverter from '../../src/conversion/buildmodelconverter';
-import buildViewConverter from '../../src/conversion/buildviewconverter';
-import ModelRange from '../../src/model/range';
-import ModelElement from '../../src/model/element';
-import ViewContainerElement from '../../src/view/containerelement';
-import ViewText from '../../src/view/text';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Widget from '@ckeditor/ckeditor5-widget/src/widget';
 import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
@@ -34,7 +41,6 @@ class FancyWidget extends Plugin {
 		const editor = this.editor;
 		const schema = editor.model.schema;
 		const data = editor.data;
-		const editing = editor.editing;
 
 		// Configure schema.
 		schema.register( 'fancywidget', {
@@ -42,19 +48,19 @@ class FancyWidget extends Plugin {
 		} );
 		schema.extend( 'fancywidget', { allowIn: '$root' } );
 
-		// Build converter from model to view for editing pipeline.
-		buildModelConverter().for( editing.modelToView )
-			.fromElement( 'fancywidget' )
-			.toElement( () => {
+		mtvElementToElement( {
+			model: 'fancywidget',
+			view: () => {
 				const widgetElement = new ViewContainerElement( 'figure', { class: 'fancy-widget' }, new ViewText( 'widget' ) );
 
 				return toWidget( widgetElement );
-			} );
+			}
+		} )( data.modelToView );
 
-		// Build converter from view element to model element for data pipeline.
-		buildViewConverter().for( data.viewToModel )
-			.fromElement( 'figure' )
-			.toElement( () => new ModelElement( 'fancywidget' ) );
+		vtmElementToElement( {
+			view: 'figure',
+			model: 'fancywidget'
+		} )( data.viewToModel );
 	}
 }
 
@@ -65,10 +71,12 @@ ClassicEditor.create( global.document.querySelector( '#editor' ), {
 	.then( editor => {
 		window.editor = editor;
 
-		buildModelConverter()
-			.for( editor.editing.modelToView )
-			.fromMarker( 'marker' )
-			.toHighlight( data => ( { class: 'highlight-' + data.markerName.split( ':' )[ 1 ] } ) );
+		mtvMarkerToHighlight( {
+			model: 'marker',
+			view: data => ( {
+				class: 'highlight-' + data.markerName.split( ':' )[ 1 ]
+			} )
+		} );
 
 		document.getElementById( 'add-marker-yellow' ).addEventListener( 'mousedown', evt => {
 			addMarker( editor, 'yellow' );

+ 34 - 20
packages/ckeditor5-engine/tests/manual/nestededitable.js

@@ -5,18 +5,24 @@
 
 /* global console */
 
+import {
+	elementToElement as vtmElementToElement
+} from '../../src/conversion/view-to-model-helpers';
+
+import {
+	elementToElement as mtvElementToElement
+} from '../../src/conversion/model-to-view-helpers';
+
+import ViewEditableElement from '../../src/view/editableelement';
+import { getData } from '../../src/dev-utils/model';
+import global from '@ckeditor/ckeditor5-utils/src/dom/global';
+
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Enter from '@ckeditor/ckeditor5-enter/src/enter';
 import Typing from '@ckeditor/ckeditor5-typing/src/typing';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Undo from '@ckeditor/ckeditor5-undo/src/undo';
-import buildModelConverter from '../../src/conversion/buildmodelconverter';
-import buildViewConverter from '../../src/conversion/buildviewconverter';
-import ViewContainerElement from '../../src/view/containerelement';
-import ViewEditableElement from '../../src/view/editableelement';
-import { getData } from '../../src/dev-utils/model';
-import global from '@ckeditor/ckeditor5-utils/src/dom/global';
 
 import './nestededitable.css';
 
@@ -25,7 +31,6 @@ class NestedEditable extends Plugin {
 		const editor = this.editor;
 		const editing = editor.editing;
 		const viewDocument = editing.view;
-		const data = editor.data;
 		const schema = editor.model.schema;
 
 		schema.register( 'figure', {
@@ -38,17 +43,24 @@ class NestedEditable extends Plugin {
 			allowIn: [ 'figure', 'figcaption' ]
 		} );
 
-		buildModelConverter().for( data.modelToView, editing.modelToView )
-			.fromElement( 'figure' )
-			.toElement( () => new ViewContainerElement( 'figure', { contenteditable: 'false' } ) );
+		editor.conversion.for( 'model' ).add( mtvElementToElement( {
+			model: 'figure',
+			view: {
+				name: 'figure',
+				attribute: {
+					contenteditable: 'false'
+				}
+			}
+		} ) );
 
-		buildViewConverter().for( data.viewToModel )
-			.fromElement( 'figure' )
-			.toElement( 'figure' );
+		editor.conversion.for( 'view' ).add( vtmElementToElement( {
+			model: 'figure',
+			view: 'figure'
+		} ) );
 
-		buildModelConverter().for( data.modelToView, editing.modelToView )
-			.fromElement( 'figcaption' )
-			.toElement( () => {
+		editor.conversion.for( 'model' ).add( mtvElementToElement( {
+			model: 'figcaption',
+			view: () => {
 				const element = new ViewEditableElement( 'figcaption', { contenteditable: 'true' } );
 				element.document = viewDocument;
 
@@ -61,11 +73,13 @@ class NestedEditable extends Plugin {
 				} );
 
 				return element;
-			} );
+			}
+		} ) );
 
-		buildViewConverter().for( data.viewToModel )
-			.fromElement( 'figcaption' )
-			.toElement( 'figcaption' );
+		editor.conversion.for( 'view' ).add( vtmElementToElement( {
+			model: 'figcaption',
+			view: 'figcaption'
+		} ) );
 	}
 }
 

+ 18 - 12
packages/ckeditor5-engine/tests/manual/tickets/475/1.js

@@ -11,8 +11,13 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Range from '../../../../src/model/range';
 import LivePosition from '../../../../src/model/liveposition';
 
-import buildModelConverter from '../../../../src/conversion/buildmodelconverter';
-import buildViewConverter from '../../../../src/conversion/buildviewconverter';
+import {
+	elementToAttribute as vtmElementToAttribute
+} from '../../../../src/conversion/view-to-model-helpers';
+
+import {
+	attributeToElement as mtvAttributeToElement,
+} from '../../../../src/conversion/model-to-view-helpers';
 
 import AttributeElement from '../../../../src/view/attributeelement';
 
@@ -24,21 +29,22 @@ import Undo from '@ckeditor/ckeditor5-undo/src/undo';
 class Link extends Plugin {
 	init() {
 		const editor = this.editor;
-		const data = editor.data;
-		const editing = editor.editing;
 
 		// Allow bold attribute on all inline nodes.
 		editor.model.schema.extend( '$text', { allowAttributes: 'link' } );
 
-		// Build converter from model to view for data and editing pipelines.
-		buildModelConverter().for( data.modelToView, editing.modelToView )
-			.fromAttribute( 'link' )
-			.toElement( href => new AttributeElement( 'a', { href } ) );
+		editor.conversion.for( 'model' ).add( mtvAttributeToElement( {
+			model: 'link',
+			view: attributeValue => new AttributeElement( 'a', { href: attributeValue } )
+		} ) );
 
-		// Build converter from view to model for data pipeline.
-		buildViewConverter().for( data.viewToModel )
-			.fromElement( 'a' )
-			.toAttribute( viewElement => ( { key: 'link', value: viewElement.getAttribute( 'href' ) } ) );
+		editor.conversion.for( 'view' ).add( vtmElementToAttribute( {
+			view: 'a',
+			model: {
+				key: 'link',
+				value: viewElement => viewElement.getAttribute( 'href' )
+			}
+		} ) );
 	}
 }
 

+ 15 - 8
packages/ckeditor5-engine/tests/tickets/699.js

@@ -8,8 +8,13 @@
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 
-import buildViewConverter from '../../src/conversion/buildviewconverter';
-import buildModelConverter from '../../src/conversion/buildmodelconverter';
+import {
+	elementToElement as vtmElementToElement
+} from '../../src/conversion/view-to-model-helpers';
+
+import {
+	elementToElement as mtvElementToElement
+} from '../../src/conversion/model-to-view-helpers';
 
 import { getData as getModelData } from '../../src/dev-utils/model';
 import { getData as getViewData } from '../../src/dev-utils/view';
@@ -49,11 +54,13 @@ function WidgetPlugin( editor ) {
 	} );
 	schema.extend( 'widget', { allowIn: '$root' } );
 
-	buildModelConverter().for( editor.data.modelToView, editor.editing.modelToView )
-		.fromElement( 'widget' )
-		.toElement( 'widget' );
+	editor.conversion.for( 'model' ).add( mtvElementToElement( {
+		model: 'widget',
+		view: 'widget'
+	} ) );
 
-	buildViewConverter().for( editor.data.viewToModel )
-		.fromElement( 'widget' )
-		.toElement( 'widget' );
+	editor.conversion.for( 'view' ).add( vtmElementToElement( {
+		model: 'widget',
+		view: 'widget'
+	} ) );
 }