@@ -222,11 +222,17 @@ class Insertion {
222222 * @param {Object } context
223223 */
224224 _handleDisallowedNode ( node , context ) {
225- // Try inserting its children (strip the parent).
225+ // If the node is an element, try inserting its children (strip the parent).
226226 if ( node . is ( 'element' ) ) {
227227 this . handleNodes ( node . getChildren ( ) , context ) ;
228228 }
229- // Try autoparagraphing.
229+ // If the node is a text and bare text is allowed in current position it means that the node
230+ // contains disallowed attributes and we have to remove them.
231+ else if ( this . schema . check ( { name : '$text' , inside : this . position } ) ) {
232+ removeDisallowedAttributes ( [ node ] , this . position , this . schema ) ;
233+ this . _handleNode ( node , context ) ;
234+ }
235+ // If text is not allowed, try autoparagraphing.
230236 else {
231237 this . _tryAutoparagraphing ( node , context ) ;
232238 }
@@ -237,7 +243,7 @@ class Insertion {
237243 */
238244 _insert ( node ) {
239245 /* istanbul ignore if */
240- if ( ! this . _checkIsAllowed ( node , [ this . position . parent ] ) ) {
246+ if ( ! this . _checkIsAllowed ( node , this . position ) ) {
241247 // Algorithm's correctness check. We should never end up here but it's good to know that we did.
242248 // Note that it would often be a silent issue if we insert node in a place where it's not allowed.
243249 log . error (
@@ -256,7 +262,7 @@ class Insertion {
256262 livePos . detach ( ) ;
257263
258264 // The last inserted object should be selected because we can't put a collapsed selection after it.
259- if ( this . _checkIsObject ( node ) && ! this . schema . check ( { name : '$text' , inside : [ this . position . parent ] } ) ) {
265+ if ( this . _checkIsObject ( node ) && ! this . schema . check ( { name : '$text' , inside : this . position } ) ) {
260266 this . nodeToSelect = node ;
261267 } else {
262268 this . nodeToSelect = null ;
@@ -282,6 +288,11 @@ class Insertion {
282288
283289 this . batch . merge ( mergePosLeft ) ;
284290
291+ // We need to check and strip disallowed attributes in all nested nodes because after merge
292+ // some attributes could end up in a path where are disallowed.
293+ const parent = position . nodeBefore ;
294+ removeDisallowedAttributes ( parent . getChildren ( ) , Position . createAt ( parent ) , this . schema , this . batch ) ;
295+
285296 this . position = Position . createFromPosition ( position ) ;
286297 position . detach ( ) ;
287298 }
@@ -305,12 +316,22 @@ class Insertion {
305316
306317 this . batch . merge ( mergePosRight ) ;
307318
319+ // We need to check and strip disallowed attributes in all nested nodes because after merge
320+ // some attributes could end up in a place where are disallowed.
321+ removeDisallowedAttributes ( position . parent . getChildren ( ) , position , this . schema , this . batch ) ;
322+
308323 this . position = Position . createFromPosition ( position ) ;
309324 position . detach ( ) ;
310325 }
311326
312327 mergePosLeft . detach ( ) ;
313328 mergePosRight . detach ( ) ;
329+
330+ // When there was no merge we need to check and strip disallowed attributes in all nested nodes of
331+ // just inserted node because some attributes could end up in a place where are disallowed.
332+ if ( ! mergeLeft && ! mergeRight ) {
333+ removeDisallowedAttributes ( node . getChildren ( ) , Position . createAt ( node ) , this . schema , this . batch ) ;
334+ }
314335 }
315336
316337 /**
@@ -325,10 +346,17 @@ class Insertion {
325346 // Do not autoparagraph if the paragraph won't be allowed there,
326347 // cause that would lead to an infinite loop. The paragraph would be rejected in
327348 // the next _handleNode() call and we'd be here again.
328- if ( this . _getAllowedIn ( paragraph , this . position . parent ) && this . _checkIsAllowed ( node , [ paragraph ] ) ) {
329- paragraph . appendChildren ( node ) ;
349+ if ( this . _getAllowedIn ( paragraph , this . position . parent ) ) {
350+ // When node is a text and is disallowed by schema it means that contains disallowed attributes
351+ // and we need to remove them.
352+ if ( node . is ( 'text' ) && ! this . _checkIsAllowed ( node , [ paragraph ] ) ) {
353+ removeDisallowedAttributes ( [ node ] , [ paragraph ] , this . schema ) ;
354+ }
330355
331- this . _handleNode ( paragraph , context ) ;
356+ if ( this . _checkIsAllowed ( node , [ paragraph ] ) ) {
357+ paragraph . appendChildren ( node ) ;
358+ this . _handleNode ( paragraph , context ) ;
359+ }
332360 }
333361 }
334362
@@ -402,31 +430,59 @@ class Insertion {
402430 */
403431 _checkIsAllowed ( node , path ) {
404432 return this . schema . check ( {
405- name : this . _getNodeSchemaName ( node ) ,
433+ name : getNodeSchemaName ( node ) ,
406434 attributes : Array . from ( node . getAttributeKeys ( ) ) ,
407435 inside : path
408436 } ) ;
409437 }
410438
411439 /**
412- * Checks wether according to the schema this is an object type element.
440+ * Checks whether according to the schema this is an object type element.
413441 *
414442 * @param {module:engine/model/node~Node } node The node to check.
415443 */
416444 _checkIsObject ( node ) {
417- return this . schema . objects . has ( this . _getNodeSchemaName ( node ) ) ;
445+ return this . schema . objects . has ( getNodeSchemaName ( node ) ) ;
418446 }
447+ }
419448
420- /**
421- * Gets a name under which we should check this node in the schema.
422- *
423- * @param {module:engine/model/node~Node } node The node.
424- */
425- _getNodeSchemaName ( node ) {
426- if ( node . is ( 'text' ) ) {
427- return '$text' ;
449+ // Gets a name under which we should check this node in the schema.
450+ //
451+ // @param {module:engine/model/node~Node } node The node.
452+ // @returns {String } Node name.
453+ function getNodeSchemaName ( node ) {
454+ return node . is ( 'text' ) ? '$text' : node . name ;
455+ }
456+
457+ // Removes disallowed by schema attributes from given nodes. When batch parameter is provided then
458+ // attributes will be removed by creating AttributeDeltas otherwise attributes will be removed
459+ // directly from provided nodes.
460+ //
461+ // @param {Array<module:engine/model/node~Node> } nodes Nodes that will be filtered.
462+ // @param {module:engine/model/schema~SchemaPath } inside Path inside which schema will be checked.
463+ // @param {module:engine/model/schema~Schema } schema Schema instance uses for element validation.
464+ // @param {module:engine/model/batch~Batch } [batch] Batch to which the deltas will be added.
465+ function removeDisallowedAttributes ( nodes , inside , schema , batch ) {
466+ for ( const node of nodes ) {
467+ const name = getNodeSchemaName ( node ) ;
468+
469+ // When node with attributes is not allowed in current position.
470+ if ( ! schema . check ( { name, inside, attributes : Array . from ( node . getAttributeKeys ( ) ) } ) ) {
471+ // Let's remove attributes one by one.
472+ // This should be improved to check all combination of attributes.
473+ for ( const attribute of node . getAttributeKeys ( ) ) {
474+ if ( ! schema . check ( { name, inside, attributes : attribute } ) ) {
475+ if ( batch ) {
476+ batch . removeAttribute ( node , attribute ) ;
477+ } else {
478+ node . removeAttribute ( attribute ) ;
479+ }
480+ }
481+ }
428482 }
429483
430- return node . name ;
484+ if ( node . is ( 'element' ) ) {
485+ removeDisallowedAttributes ( node . getChildren ( ) , Position . createAt ( node ) , schema , batch ) ;
486+ }
431487 }
432488}
0 commit comments