Răsfoiți Sursa

Added EmptyElement support to view utilities.

Szymon Kupś 9 ani în urmă
părinte
comite
04c0236a14

+ 44 - 26
packages/ckeditor5-engine/src/dev-utils/view.js

@@ -20,6 +20,7 @@ import Range from '../view/range.js';
 import Position from '../view/position.js';
 import AttributeElement from '../view/attributeelement.js';
 import ContainerElement from '../view/containerelement.js';
+import EmptyElement from '../view/emptyelement.js';
 import ViewText from '../view/text.js';
 
 const ELEMENT_RANGE_START_TOKEN = '[';
@@ -38,7 +39,7 @@ const TEXT_RANGE_END_TOKEN = '}';
  * @param {Boolean} [options.rootName='main'] Name of the root from which data should be stringified. If not provided
  * default `main` name will be used.
  * @param {Boolean} [options.showType=false] When set to `true` type of elements will be printed (`<container:p>`
- * instead of `<p>` and `<attribute:b>` instead of `<b>`).
+ * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
  * @param {Boolean} [options.showPriority=false] When set to `true` AttributeElement's priority will be printed
  * (`<span view-priority="12">`, `<b view-priority="10">`).
  * @returns {String} The stringified data.
@@ -162,13 +163,15 @@ setData._parse = parse;
  *
  * Additional options object can be provided.
  * If `options.showType` is set to `true`, element's types will be
- * presented for {@link engine.view.AttributeElement AttributeElements} and {@link engine.view.ContainerElement
- * ContainerElements}:
+ * presented for {@link engine.view.AttributeElement AttributeElements}, {@link engine.view.ContainerElement
+ * ContainerElements} and {@link engine.view.EmptyElement EmptyElements}:
  *
  *		const attribute = new AttributeElement( 'b' );
  *		const container = new ContainerElement( 'p' );
+ *		const empty = new EmptyElement( 'img' );
  *		getData( attribute, null, { showType: true } ); // '<attribute:b></attribute:b>'
  *		getData( container, null, { showType: true } ); // '<container:p></container:p>'
+ *		getData( empty, null, { showType: true } ); // '<empty:img></empty:img>'
  *
  * If `options.showPriority` is set to `true`, priority will be displayed for all
  * {@link engine.view.AttributeElement AttributeElements}.
@@ -185,7 +188,7 @@ setData._parse = parse;
  * containing one range collapsed at this position.
  * @param {Object} [options] Object with additional options.
  * @param {Boolean} [options.showType=false] When set to `true` type of elements will be printed (`<container:p>`
- * instead of `<p>` and `<attribute:b>` instead of `<b>`).
+ * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
  * @param {Boolean} [options.showPriority=false] When set to `true` AttributeElement's priority will be printed
  * (`<span view-priority="12">`, `<b view-priority="10">`).
  * @param {Boolean} [options.ignoreRoot=false] When set to `true` root's element opening and closing will not be printed.
@@ -288,7 +291,7 @@ export function parse( data, options = {} ) {
 		sameSelectionCharacters: options.sameSelectionCharacters
 	} );
 	const processor = new XmlDataProcessor( {
-		namespaces: [ 'attribute', 'container' ]
+		namespaces: [ 'attribute', 'container', 'empty' ]
 	} );
 
 	// Convert data to view.
@@ -560,8 +563,8 @@ class ViewStringify {
 	 * @param root
 	 * @param {engine.view.Selection} [selection=null] Selection which ranges should be also converted to string.
 	 * @param {Object} [options] Options object.
-	 * @param {Boolean} [options.showType=false] When set to `true` type of elements will be printed ( `<container:p>`
-	 * instead of `<p>` and `<attribute:b>` instead of `<b>`.
+	 * @param {Boolean} [options.showType=false] When set to `true` type of elements will be printed (`<container:p>`
+	 * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
 	 * @param {Boolean} [options.showPriority=false] When set to `true` AttributeElement's priority will be printed.
 	 * @param {Boolean} [options.ignoreRoot=false] When set to `true` root's element opening and closing tag will not
 	 * be outputted.
@@ -719,9 +722,9 @@ class ViewStringify {
 
 	/**
 	 * Converts passed {@link engine.view.Element Element} to opening tag.
-	 * Depending on current configuration opening tag can be simple (`<a>`), contain type prefix (`<container:p>` or
-	 * `<attribute:a>`), contain priority information ( `<attribute:a view-priority="20">` ). Element's attributes also
-	 * will be included (`<a href="http://ckeditor.com" name="foobar">`).
+	 * Depending on current configuration opening tag can be simple (`<a>`), contain type prefix (`<container:p>`,
+	 * `<attribute:a>` or `<empty:img>`), contain priority information ( `<attribute:a view-priority="20">` ).
+	 * Element's attributes also will be included (`<a href="http://ckeditor.com" name="foobar">`).
 	 *
 	 * @private
 	 * @param {engine.view.Element} element
@@ -740,8 +743,8 @@ class ViewStringify {
 
 	/**
 	 * Converts passed {@link engine.view.Element Element} to closing tag.
-	 * Depending on current configuration closing tag can be simple (`</a>`) or contain type prefix (`</container:p>` or
-	 * `</attribute:a>`).
+	 * Depending on current configuration closing tag can be simple (`</a>`) or contain type prefix (`</container:p>`,
+	 * `</attribute:a>` or `</empty:img>`).
 	 *
 	 * @private
 	 * @param {engine.view.Element} element
@@ -756,9 +759,10 @@ class ViewStringify {
 
 	/**
 	 * Converts passed {@link engine.view.Element Element's} type to its string representation
-	 * Returns 'attribute' for {@link engine.view.AttributeElement AttributeElements} and
-	 * 'container' for {@link engine.view.ContainerElement ContainerElements}. Returns empty string when current
-	 * configuration is preventing showing elements' types.
+	 * Returns 'attribute' for {@link engine.view.AttributeElement AttributeElements},
+	 * 'container' for {@link engine.view.ContainerElement ContainerElements} and 'empty' for
+	 * {@link engine.view.EmptyElement EmptyElements}. Returns empty string when current configuration is preventing
+	 * showing elements' types.
 	 *
 	 * @private
 	 * @param {engine.view.Element} element
@@ -773,6 +777,10 @@ class ViewStringify {
 			if ( element instanceof ContainerElement ) {
 				return 'container';
 			}
+
+			if ( element instanceof EmptyElement ) {
+				return 'empty';
+			}
 		}
 
 		return '';
@@ -816,13 +824,14 @@ class ViewStringify {
 	}
 }
 
-// Converts {@link engine.view.Element Elements} to {@link engine.view.AttributeElement AttributeElements} and
-// {@link engine.view.ContainerElement ContainerElements}. It converts whole tree starting from the `rootNode`.
-// Conversion is based on element names. See `_convertElement` method for more details.
+// Converts {@link engine.view.Element Elements} to {@link engine.view.AttributeElement AttributeElements},
+// {@link engine.view.ContainerElement ContainerElements} or {@link engine.view.EmptyElement EmptyElements}.
+// It converts whole tree starting from the `rootNode`. Conversion is based on element names.
+// See `_convertElement` method for more details.
 //
 // @param {engine.view.Element|engine.view.DocumentFragment|engine.view.Text} rootNode Root node to convert.
 // @returns {engine.view.Element|engine.view.DocumentFragment|engine.view.Text|engine.view.AttributeElement|
-// engine.view.ContainerElement} Root node of converted elements.
+// engine.view.ContainerElement|engine.view.EmptyElement} Root node of converted elements.
 function _convertViewElements( rootNode ) {
 	const isFragment = rootNode instanceof ViewDocumentFragment;
 
@@ -832,6 +841,10 @@ function _convertViewElements( rootNode ) {
 
 		// Convert all child nodes.
 		for ( let child of rootNode.getChildren() ) {
+			if ( convertedElement instanceof EmptyElement ) {
+				throw new Error( `Parse error - cannot parse inside EmptyElement.` );
+			}
+
 			convertedElement.appendChildren( _convertViewElements( child ) );
 		}
 
@@ -841,12 +854,14 @@ function _convertViewElements( rootNode ) {
 	return rootNode;
 }
 
-// Converts {@link engine.view.Element Element} to {@link engine.view.AttributeElement AttributeElement} or
-// {@link engine.view.ContainerElement ContainerElement}.
+// Converts {@link engine.view.Element Element} to {@link engine.view.AttributeElement AttributeElement},
+// {@link engine.view.ContainerElement ContainerElement} or {@link engine.view.EmptyElement EmptyElement}.
 // If element's name is in format `attribute:b` with `view-priority="11"` attribute it will be converted to
 // {@link engine.view.AttributeElement AttributeElement} with priority 11.
 // If element's name is in format `container:p` - it will be converted to
 // {@link engine.view.ContainerElement ContainerElement}.
+// If element's name is in format `empty:img` - it will be converted to
+// {@link engine.view.EmptyElement EmptyElement}.
 // If element's name will not contain any additional information - {@link engine.view.Element view Element} will be
 // returned.
 //
@@ -865,6 +880,8 @@ function _convertElement( viewElement ) {
 		}
 	} else if ( info.type == 'container' ) {
 		newElement = new ContainerElement( info.name );
+	} else if ( info.type == 'empty' ) {
+		newElement = new EmptyElement( info.name );
 	} else {
 		newElement = new ViewElement( info.name );
 	}
@@ -878,14 +895,15 @@ function _convertElement( viewElement ) {
 }
 
 // Converts `view-priority` attribute and {@link engine.view.Element#name Element's name} information needed for creating
-// {@link engine.view.AttributeElement AttributeElement} or {@link engine.view.ContainerElement ContainerElement} instance.
+// {@link engine.view.AttributeElement AttributeElement}, {@link engine.view.ContainerElement ContainerElement} or
+// {@link engine.view.EmptyElement EmptyElement} instance.
 // Name can be provided in two formats: as a simple element's name (`div`), or as a type and name (`container:div`,
-// `attribute:span`);
+// `attribute:span`, `empty:img`);
 //
 // @param {engine.view.Element} element Element which name should be converted.
 // @returns {Object} info Object with parsed information.
 // @returns {String} info.name Parsed name of the element.
-// @returns {String|null} info.type Parsed type of the element, can be `attribute` or `container`.
+// @returns {String|null} info.type Parsed type of the element, can be `attribute`, `container` or `empty`.
 // returns {Number|null} info.priority Parsed priority of the element.
 function _convertElementNameAndPriority( viewElement ) {
 	const parts = viewElement.name.split( ':' );
@@ -914,12 +932,12 @@ function _convertElementNameAndPriority( viewElement ) {
 	throw new Error( `Parse error - cannot parse element's name: ${ viewElement.name }.` );
 }
 
-// Checks if element's type is allowed. Returns `attribute`, `container` or `null`.
+// Checks if element's type is allowed. Returns `attribute`, `container`, `empty` or `null`.
 //
 // @param {String} type
 // @returns {String|null}
 function _convertType( type ) {
-	if ( type == 'container' || type == 'attribute' ) {
+	if ( type == 'container' || type == 'attribute' || type == 'empty' ) {
 		return type;
 	}
 

+ 20 - 0
packages/ckeditor5-engine/tests/dev-utils/view.js

@@ -12,6 +12,7 @@ import Position from 'ckeditor5/engine/view/position.js';
 import Element from 'ckeditor5/engine/view/element.js';
 import AttributeElement from 'ckeditor5/engine/view/attributeelement.js';
 import ContainerElement from 'ckeditor5/engine/view/containerelement.js';
+import EmptyElement from 'ckeditor5/engine/view/emptyelement.js';
 import Text from 'ckeditor5/engine/view/text.js';
 import Selection from 'ckeditor5/engine/view/selection.js';
 import Range from 'ckeditor5/engine/view/range.js';
@@ -332,6 +333,13 @@ describe( 'view test utils', () => {
 			const string = stringify( text, range );
 			expect( string ).to.equal( 'foo{b}ar' );
 		} );
+
+		it( 'should stringify EmptyElement', () => {
+			const img = new EmptyElement( 'img' );
+			const p = new ContainerElement( 'p', null, img );
+			expect( stringify( p, null, { showType: true } ) )
+				.to.equal( '<container:p><empty:img></empty:img></container:p>' );
+		} );
 	} );
 
 	describe( 'parse', () => {
@@ -612,5 +620,17 @@ describe( 'view test utils', () => {
 
 			expect( stringify( data ) ).to.equal( '<p><span>text</span><b>test</b></p>' );
 		} );
+
+		it( 'should parse EmptyElement', () => {
+			const parsed = parse( '<empty:img></empty:img>' );
+
+			expect( parsed ).to.be.instanceof( EmptyElement );
+		} );
+
+		it( 'should throw an error if EmptyElement is not empty', () => {
+			expect( () => {
+				parse( '<empty:img>foo bar</empty:img>' );
+			} ).to.throw( Error, 'Parse error - cannot parse inside EmptyElement.' );
+		} );
 	} );
 } );