8
0
فهرست منبع

Added treeModel.Schema#checkInElements. Changed treeModel.Schema#requireAttributes to work on sets of attributes ("patterns").

Szymon Cofalik 9 سال پیش
والد
کامیت
0619c61125
2فایلهای تغییر یافته به همراه109 افزوده شده و 26 حذف شده
  1. 52 20
      packages/ckeditor5-engine/src/treemodel/schema.js
  2. 57 6
      packages/ckeditor5-engine/tests/treemodel/schema/schema.js

+ 52 - 20
packages/ckeditor5-engine/src/treemodel/schema.js

@@ -78,14 +78,14 @@ export class SchemaItem {
 	}
 	}
 
 
 	/**
 	/**
-	 * Specifies that the entity requires this attribute to be set.
+	 * Specifies that the entity, to be valid, requires given attributes set. It is possible to register multiple
+	 * different attributes set. If there are more than one attributes set required, the entity will be valid if
+	 * at least one of them is fulfilled.
 	 *
 	 *
-	 * @param {String} attribute Attribute name that has to be set.
+	 * @param {Array.<String>} attributes Attributes that has to be set on the entity to make it valid.
 	 */
 	 */
-	requireAttribute( attribute ) {
-		if ( this._requiredAttributes.indexOf( attribute ) == -1 ) {
-			this._requiredAttributes.push( attribute );
-		}
+	requireAttributes( attributes ) {
+		this._requiredAttributes.push( attributes );
 	}
 	}
 
 
 	/**
 	/**
@@ -271,19 +271,13 @@ export default class Schema {
 	}
 	}
 
 
 	/**
 	/**
-	 * Makes a requirement in schema that entity represented by given item has to have given attribute set.
+	 * Makes a requirement in schema that entity represented by given item has to have given set of attributes.
 	 *
 	 *
 	 * @param {String} name Entity name.
 	 * @param {String} name Entity name.
-	 * @param {Array.<String>|String} attributes Attributes that are required.
+	 * @param {Array.<String>} attributes Attributes that has to be set on the entity to make it valid.
 	 */
 	 */
 	requireAttributes( name, attributes ) {
 	requireAttributes( name, attributes ) {
-		if ( typeof attributes == 'string' || attributes instanceof String ) {
-			attributes = [ attributes ];
-		}
-
-		for ( let attribute of attributes ) {
-			this._getItem( name ).requireAttribute( attribute );
-		}
+		this._getItem( name ).requireAttributes( attributes );
 	}
 	}
 
 
 	/**
 	/**
@@ -310,6 +304,27 @@ export default class Schema {
 		} );
 		} );
 	}
 	}
 
 
+	/**
+	 * Checks whether entity with given name (and optionally, with given attribute(s)) is allowed in given chain of
+	 * parent {@link engine.treeModel.Element elements}.
+	 *
+	 * @param {Array.<engine.treeModel.Element>} elements Elements that are parents of queried entity.
+	 * @param {String} name Entity name to check.
+	 * @param {Array.<String>|String} [attributes] If set, schema will check for entity with given attribute(s).
+	 * @returns {Boolean} `true` if entity is allowed, `false` otherwise
+	 */
+	checkInElements( elements, name, attributes ) {
+		if ( !this.hasItem( name ) ) {
+			return false;
+		}
+
+		return this.checkQuery( {
+			name: name,
+			inside: elements.map( ( element ) => element.name ),
+			attributes: attributes
+		} );
+	}
+
 	/**
 	/**
 	 * Checks whether given query is allowed in schema.
 	 * Checks whether given query is allowed in schema.
 	 *
 	 *
@@ -339,12 +354,28 @@ export default class Schema {
 		const schemaItems = this._extensionChains.get( query.name ).map( ( name ) => {
 		const schemaItems = this._extensionChains.get( query.name ).map( ( name ) => {
 			return this._getItem( name );
 			return this._getItem( name );
 		} );
 		} );
-
-		// First check if the query has all attributes that are required by this item.
 		const baseItem = this._getItem( query.name );
 		const baseItem = this._getItem( query.name );
 
 
-		for ( let attribute of baseItem._requiredAttributes ) {
-			if ( query.attributes.indexOf( attribute ) == -1 ) {
+		// First check if the query meets at least one of required sets of attributes for this item (if there are any).
+		if ( baseItem._requiredAttributes.length > 0 ) {
+			let found;
+
+			for ( let attributes of baseItem._requiredAttributes ) {
+				found = true;
+
+				for ( let attribute of attributes ) {
+					if ( query.attributes.indexOf( attribute ) == -1 ) {
+						found = false;
+						break;
+					}
+				}
+
+				if ( found ) {
+					break;
+				}
+			}
+
+			if ( !found ) {
 				return false;
 				return false;
 			}
 			}
 		}
 		}
@@ -362,6 +393,8 @@ export default class Schema {
 		// If there are correct allow paths that match the query, this query is valid with schema.
 		// If there are correct allow paths that match the query, this query is valid with schema.
 		// Since we are supporting multiple attributes, we have to make sure that if attributes are set,
 		// Since we are supporting multiple attributes, we have to make sure that if attributes are set,
 		// we have allowed paths for all of them.
 		// we have allowed paths for all of them.
+		// Keep in mind that if the query has no attributes, query.attribute was converted to an array
+		// with a single `undefined` value. This fits the algorithm well.
 		for ( let attribute of query.attributes ) {
 		for ( let attribute of query.attributes ) {
 			let matched = false;
 			let matched = false;
 
 
@@ -379,7 +412,6 @@ export default class Schema {
 			}
 			}
 		}
 		}
 
 
-		// All attributes were allowed.
 		return true;
 		return true;
 	}
 	}
 
 

+ 57 - 6
packages/ckeditor5-engine/tests/treemodel/schema/schema.js

@@ -192,6 +192,50 @@ describe( 'checkAtPosition', () => {
 	} );
 	} );
 } );
 } );
 
 
+describe( 'checkInElements', () => {
+	beforeEach( () => {
+		schema.registerItem( 'div', '$block' );
+		schema.registerItem( 'header', '$block' );
+		schema.registerItem( 'p', '$block' );
+		schema.registerItem( 'img', '$inline' );
+
+		schema.allow( { name: '$block', inside: 'div' } );
+		schema.allow( { name: '$inline', attributes: 'bold', inside: '$block' } );
+
+		schema.disallow( { name: '$inline', attributes: 'bold', inside: 'header' } );
+	} );
+
+	it( 'should return true if given element is allowed by schema at given position', () => {
+		// P is block and block is allowed in DIV.
+		expect( schema.checkInElements( [ new Element( 'div' ) ], 'p' ) ).to.be.true;
+
+		// IMG is inline and inline is allowed in block.
+		expect( schema.checkInElements( [ new Element( 'div' ) ], 'img' ) ).to.be.true;
+		expect( schema.checkInElements( [ new Element( 'p' ) ], 'img' ) ).to.be.true;
+
+		// Inline is allowed in any block and is allowed with attribute bold.
+		expect( schema.checkInElements( [ new Element( 'div' ) ], 'img', [ 'bold' ] ) ).to.be.true;
+		expect( schema.checkInElements( [ new Element( 'p' ) ], 'img', [ 'bold' ] ) ).to.be.true;
+
+		// Inline is allowed in header which is allowed in DIV.
+		expect( schema.checkInElements( [ new Element( 'div' ) ], 'header' ) ).to.be.true;
+		expect( schema.checkInElements( [ new Element( 'header' ) ], 'img' ) ).to.be.true;
+		expect( schema.checkInElements( [ new Element( 'div' ), new Element( 'header' ) ], 'img' ) ).to.be.true;
+	} );
+
+	it( 'should return false if given element is not allowed by schema at given position', () => {
+		// P with attribute is not allowed.
+		expect( schema.checkInElements( [ new Element( 'div' ) ], 'p', 'bold' ) ).to.be.false;
+
+		// Bold text is not allowed in header
+		expect( schema.checkInElements( [ new Element( 'header' ) ], '$text', 'bold' ) ).to.be.false;
+	} );
+
+	it( 'should return false if given element is not registered in schema', () => {
+		expect( schema.checkInElements( [ new Element( 'div' ) ], 'new' ) ).to.be.false;
+	} );
+} );
+
 describe( 'checkQuery', () => {
 describe( 'checkQuery', () => {
 	it( 'should return false if given element is not registered in schema', () => {
 	it( 'should return false if given element is not registered in schema', () => {
 		expect( schema.checkQuery( { name: 'new', inside: [ 'div', 'header' ] } ) ).to.be.false;
 		expect( schema.checkQuery( { name: 'new', inside: [ 'div', 'header' ] } ) ).to.be.false;
@@ -210,14 +254,21 @@ describe( 'checkQuery', () => {
 	} );
 	} );
 
 
 	it( 'should support required attributes', () => {
 	it( 'should support required attributes', () => {
-		schema.registerItem( 'img', '$inline' );
-		schema.requireAttributes( 'img', 'src' );
-		schema.allow( { name: 'img', inside: '$block', attributes: [ 'src' ] } );
+		schema.registerItem( 'a', '$inline' );
+		schema.requireAttributes( 'a', [ 'name' ] );
+		schema.requireAttributes( 'a', [ 'href' ] );
+		schema.allow( { name: 'a', inside: '$block', attributes: [ 'name', 'href', 'title', 'target' ] } );
+
+		// Even though a is allowed in $block thanks to inheriting from $inline, we require href or name attribute.
+		expect( schema.checkQuery( { name: 'a', inside: '$block' } ) ).to.be.false;
 
 
-		// Even though img is allowed in $block thanks to inheriting from $inline, we require src attribute.
-		expect( schema.checkQuery( { name: 'img', inside: '$block' } ) ).to.be.false;
+		// Even though a with title is allowed, we have to meet at least on required attributes set.
+		expect( schema.checkQuery( { name: 'a', inside: '$block', attributes: [ 'title' ] } ) ).to.be.false;
 
 
-		expect( schema.checkQuery( { name: 'img', inside: '$block', attributes: [ 'src' ] } ) ).to.be.true;
+		expect( schema.checkQuery( { name: 'a', inside: '$block', attributes: [ 'name' ] } ) ).to.be.true;
+		expect( schema.checkQuery( { name: 'a', inside: '$block', attributes: [ 'href' ] } ) ).to.be.true;
+		expect( schema.checkQuery( { name: 'a', inside: '$block', attributes: [ 'name', 'href' ] } ) ).to.be.true;
+		expect( schema.checkQuery( { name: 'a', inside: '$block', attributes: [ 'name', 'title', 'target' ] } ) ).to.be.true;
 	} );
 	} );
 
 
 	it( 'should support multiple attributes', () => {
 	it( 'should support multiple attributes', () => {