Explorar el Código

Added support for specifying context as a position and array of element names.

Piotrek Koszuliński hace 8 años
padre
commit
95d4e01366

+ 30 - 11
packages/ckeditor5-engine/src/model/schema.js

@@ -23,11 +23,11 @@ export default class Schema {
 		this.decorate( 'checkAttribute' );
 
 		this.on( 'checkAttribute', ( evt, args ) => {
-			args[ 0 ] = getContext( args[ 0 ] );
+			args[ 0 ] = normalizeContext( args[ 0 ] );
 		}, { priority: 'highest' } );
 
 		this.on( 'checkChild', ( evt, args ) => {
-			args[ 0 ] = getContext( args[ 0 ] );
+			args[ 0 ] = normalizeContext( args[ 0 ] );
 		}, { priority: 'highest' } );
 	}
 
@@ -65,6 +65,9 @@ export default class Schema {
 		return this._compiledRules;
 	}
 
+	/**
+	 * @param {module:engine/model/node~Node|String} item
+	 */
 	getRule( item ) {
 		let itemName;
 
@@ -103,6 +106,10 @@ export default class Schema {
 		return !!( rule && rule.isObject );
 	}
 
+	/**
+	 * @param {module:engine/model/element~Element|module:engine/model/position~Position|Array.<String>} context
+	 * @param {module:engine/model/node~Node|String}
+	 */
 	checkChild( context, child ) {
 		const rule = this.getRule( child );
 
@@ -298,15 +305,27 @@ function getAllowedChildren( compiledRules, itemName ) {
 	return getValues( compiledRules ).filter( rule => rule.allowIn.includes( itemRule.name ) );
 }
 
-function getContext( node ) {
-	return node.getAncestors( { includeSelf: true } ).map( node => {
-		return {
-			name: node.is( 'text' ) ? '$text' : node.name,
-			* getAttributes() {
-				yield* node.getAttributes();
-			}
-		};
-	} );
+function normalizeContext( ctx ) {
+	// See the comment in tests about specifying checkChild()'s context as an array.
+	if ( Array.isArray( ctx ) ) {
+		return ctx.map( nodeName => {
+			return {
+				name: nodeName,
+				* getAttributes() {}
+			};
+		} );
+	}
+	// Item or position (PS. It's ok that Position#getAncestors() doesn't accept params).
+	else {
+		return ctx.getAncestors( { includeSelf: true } ).map( node => {
+			return {
+				name: node.is( 'text' ) ? '$text' : node.name,
+				* getAttributes() {
+					yield* node.getAttributes();
+				}
+			};
+		} );
+	}
 }
 
 function getValues( obj ) {

+ 51 - 4
packages/ckeditor5-engine/tests/model/schema.js

@@ -7,6 +7,8 @@ import Schema from '../../src/model/schema';
 
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import Element from '../../src/model/element';
+import Position from '../../src/model/position';
+import Text from '../../src/model/text';
 
 describe( 'Schema', () => {
 	let schema, root1, r1p1, r1p2, r1bQ, r1bQp, root2;
@@ -275,6 +277,53 @@ describe( 'Schema', () => {
 	} );
 
 	describe( 'checkChild()', () => {
+		beforeEach( () => {
+			schema.register( '$root' );
+			schema.register( 'paragraph', {
+				allowIn: '$root'
+			} );
+			schema.register( '$text', {
+				allowIn: 'paragraph'
+			} );
+		} );
+
+		it( 'accepts an element as a context and a node name as a child', () => {
+			expect( schema.checkChild( root1, 'paragraph' ) ).to.be.true;
+			expect( schema.checkChild( root1, '$text' ) ).to.be.false;
+		} );
+
+		it( 'accepts a position as a context', () => {
+			const posInRoot = Position.createAt( root1 );
+			const posInParagraph = Position.createAt( r1p1 );
+
+			expect( schema.checkChild( posInRoot, 'paragraph' ) ).to.be.true;
+			expect( schema.checkChild( posInRoot, '$text' ) ).to.be.false;
+
+			expect( schema.checkChild( posInParagraph, '$text' ) ).to.be.true;
+			expect( schema.checkChild( posInParagraph, 'paragraph' ) ).to.be.false;
+		} );
+
+		// This is a temporary feature which is needed to make the current V->M conversion works.
+		// It should be removed once V->M conversion uses real positions.
+		// Of course, real positions have this advantage that we know element attributes at this point.
+		it( 'accepts an array of element names as a context', () => {
+			const contextInRoot = [ '$root' ];
+			const contextInParagraph = [ '$root', 'paragraph' ];
+
+			expect( schema.checkChild( contextInRoot, 'paragraph' ) ).to.be.true;
+			expect( schema.checkChild( contextInRoot, '$text' ) ).to.be.false;
+
+			expect( schema.checkChild( contextInParagraph, '$text' ) ).to.be.true;
+			expect( schema.checkChild( contextInParagraph, 'paragraph' ) ).to.be.false;
+		} );
+
+		it( 'accepts a node as a child', () => {
+			expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
+			expect( schema.checkChild( root1, new Text( 'foo' ) ) ).to.be.false;
+		} );
+	} );
+
+	describe( 'rules compilation', () => {
 		describe( 'allowIn cases', () => {
 			it( 'passes $root>paragraph', () => {
 				schema.register( '$root' );
@@ -699,7 +748,7 @@ describe( 'Schema', () => {
 
 		// We need to handle cases where some independent features registered rules which might use
 		// optional elements (elements which might not have been registered).
-		describe( 'missing rules', () => {
+		describe( 'missing structure rules', () => {
 			it( 'does not break when trying to check a child which is not registered', () => {
 				schema.register( '$root' );
 
@@ -765,9 +814,7 @@ describe( 'Schema', () => {
 				expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
 			} );
 		} );
-	} );
 
-	describe( 'checkAttribute()', () => {
 		describe( 'allowAttributes', () => {
 			it( 'passes paragraph[align]', () => {
 				schema.register( 'paragraph', {
@@ -868,7 +915,7 @@ describe( 'Schema', () => {
 			} );
 		} );
 
-		describe( 'missing rules', () => {
+		describe( 'missing attribute rules', () => {
 			it( 'does not crash when checking an attribute of a unregistered element', () => {
 				expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.false;
 			} );