Sfoglia il codice sorgente

Introduced Schema#getValidRanges.

Piotrek Koszuliński 8 anni fa
parent
commit
abb12f2fbe

+ 44 - 0
packages/ckeditor5-engine/src/model/schema.js

@@ -13,6 +13,8 @@ import clone from '@ckeditor/ckeditor5-utils/src/lib/lodash/clone';
 import isArray from '@ckeditor/ckeditor5-utils/src/lib/lodash/isArray';
 import isString from '@ckeditor/ckeditor5-utils/src/lib/lodash/isString';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+import TreeWalker from './treewalker';
+import Range from './range';
 
 /**
  * Schema is a definition of the structure of the document. It allows to define which tree model items (element, text, etc.)
@@ -342,6 +344,48 @@ export default class Schema {
 	}
 
 	/**
+	 * Transforms the given set ranges into a set of ranges where the given attribute is allowed (and can be applied).
+	 *
+	 * @param {Array.<module:engine/model/range~Range>} ranges Ranges to be validated.
+	 * @param {String} attribute The name of the attribute to check.
+	 * @returns {Array.<module:engine/model/range~Range>} Ranges in which the attribute is allowed.
+	 */
+	getValidRanges( ranges, attribute ) {
+		const validRanges = [];
+
+		for ( const range of ranges ) {
+			const walker = new TreeWalker( { boundaries: range, mergeCharacters: true } );
+			let step = walker.next();
+
+			let last = range.start;
+			let from = range.start;
+			const to = range.end;
+
+			while ( !step.done ) {
+				const name = step.value.item.name || '$text';
+				const itemPosition = Position.createBefore( step.value.item );
+
+				if ( !this.check( { name, inside: itemPosition, attributes: attribute } ) ) {
+					if ( !from.isEqual( last ) ) {
+						validRanges.push( new Range( from, last ) );
+					}
+
+					from = walker.position;
+				}
+
+				last = walker.position;
+				step = walker.next();
+			}
+
+			if ( from && !from.isEqual( to ) ) {
+				validRanges.push( new Range( from, to ) );
+			}
+		}
+
+		return validRanges;
+	}
+
+	/**
 	 * Returns {@link module:engine/model/schema~SchemaItem schema item} that was registered in the schema under given name.
 	 * If item has not been found, throws error.
 	 *

+ 76 - 1
packages/ckeditor5-engine/tests/model/schema/schema.js

@@ -7,9 +7,11 @@ import { default as Schema, SchemaItem } from '../../../src/model/schema';
 import Document from '../../../src/model/document';
 import Element from '../../../src/model/element';
 import Position from '../../../src/model/position';
+import Range from '../../../src/model/range';
+import Selection from '../../../src/model/selection';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
-import { setData } from '../../../src/dev-utils/model';
+import { setData, stringify } from '../../../src/dev-utils/model';
 
 testUtils.createSinonSandbox();
 
@@ -540,4 +542,77 @@ describe( 'Schema', () => {
 			} );
 		} );
 	} );
+
+	describe( 'getValidRanges()', () => {
+		const attribute = 'bold';
+		let document, root, schema, ranges;
+
+		beforeEach( () => {
+			document = new Document();
+			schema = document.schema;
+			root = document.createRoot();
+
+			schema.registerItem( 'p', '$block' );
+			schema.registerItem( 'h1', '$block' );
+			schema.registerItem( 'img', '$inline' );
+
+			schema.allow( { name: '$text', attributes: 'bold', inside: 'p' } );
+			schema.allow( { name: 'p', attributes: 'bold', inside: '$root' } );
+
+			setData( document, '<p>foo<img />bar</p>' );
+			ranges = [ Range.createOn( root.getChild( 0 ) ) ];
+		} );
+
+		it( 'should return unmodified ranges when attribute is allowed on each item (text is not allowed in img)', () => {
+			schema.allow( { name: 'img', attributes: 'bold', inside: 'p' } );
+
+			expect( schema.getValidRanges( ranges, attribute ) ).to.deep.equal( ranges );
+		} );
+
+		it( 'should return unmodified ranges when attribute is allowed on each item (text is allowed in img)', () => {
+			schema.allow( { name: 'img', attributes: 'bold', inside: 'p' } );
+			schema.allow( { name: '$text', inside: 'img' } );
+
+			expect( schema.getValidRanges( ranges, attribute ) ).to.deep.equal( ranges );
+		} );
+
+		it( 'should return two ranges when attribute is not allowed on one item', () => {
+			schema.allow( { name: 'img', attributes: 'bold', inside: 'p' } );
+			schema.allow( { name: '$text', inside: 'img' } );
+
+			setData( document, '<p>foo<img>xxx</img>bar</p>' );
+
+			const validRanges = schema.getValidRanges( ranges, attribute );
+			const sel = new Selection();
+			sel.setRanges( validRanges );
+
+			expect( stringify( root, sel ) ).to.equal( '[<p>foo<img>]xxx[</img>bar</p>]' );
+		} );
+
+		it( 'should return three ranges when attribute is not allowed on one element but is allowed on its child', () => {
+			schema.allow( { name: '$text', inside: 'img' } );
+			schema.allow( { name: '$text', attributes: 'bold', inside: 'img' } );
+
+			setData( document, '<p>foo<img>xxx</img>bar</p>' );
+
+			const validRanges = schema.getValidRanges( ranges, attribute );
+			const sel = new Selection();
+			sel.setRanges( validRanges );
+
+			expect( stringify( root, sel ) ).to.equal( '[<p>foo]<img>[xxx]</img>[bar</p>]' );
+		} );
+
+		it( 'should split range into two ranges and omit disallowed element', () => {
+			// Disallow bold on img.
+			document.schema.disallow( { name: 'img', attributes: 'bold', inside: 'p' } );
+
+			const result = schema.getValidRanges( ranges, attribute );
+
+			expect( result ).to.length( 2 );
+			expect( result[ 0 ].start.path ).to.members( [ 0 ] );
+			expect( result[ 0 ].end.path ).to.members( [ 0, 3 ] );
+			expect( result[ 1 ].start.path ).to.members( [ 0, 4 ] );
+			expect( result[ 1 ].end.path ).to.members( [ 1 ] );
+		} );
+	} );
 } );