Mapper (engine/conversion)
@ckeditor/ckeditor5-engine/src/conversion/mapper
Maps elements, positions and markers between the view and the model.
Mapper use bound elements to find corresponding elements and positions, so, to get proper results, all model elements should be bound.
To map complex model to/from view relations, you may provide custom callbacks for
modelToViewPosition event and
viewToModelPosition event that are fired whenever
a position mapping request occurs.
Those events are fired by toViewPosition
and toModelPosition methods. Mapper
adds it's own default callbacks
with 'lowest'
priority. To override default Mapper
mapping, add custom callback with higher priority and
stop the event.
Filtering
Properties
-
_markerNameToElements : Map
private
Model marker name to view elements mapping.
Keys are
String
s while values areSet
s with view elements. One marker (name) can be mapped to multiple elements. -
_modelToViewMapping : WeakMap
private
Model element to view element mapping.
-
_viewToModelLengthCallbacks : Map
private
A map containing callbacks between view element names and functions evaluating length of view elements in model.
-
_viewToModelMapping : WeakMap
private
View element to model element mapping.
Methods
-
Creates an instance of the mapper.
-
bindElementToMarker( element, name )
Binds given marker name with given view element. The element will be added to the current set of elements bound with given marker name.
Parameters
element : Element
Element to bind.
name : String
Marker name.
-
bindElements( modelElement, viewElement )
Marks model and view elements as corresponding. Corresponding elements can be retrieved by using the toModelElement and toViewElement methods. The information that elements are bound is also used to translate positions.
-
Removes all model to view and view to model bindings.
-
getModelLength( viewNode ) → Number
Gets the length of the view element in the model.
The length is calculated as follows:
- if length mapping callback is provided for given
viewNode
it is used to evaluate model length (viewNode
is used as first and only parameter passed to the callback), - length of a text node is equal to the length of it's data,
- length of a ui element is equal to 0,
- length of a mapped element is equal to 1,
- length of a not-mapped element is equal to the length of it's children.
Examples:
foo -> 3 // Text length is equal to it's data length. <p>foo</p> -> 1 // Length of an element which is mapped is by default equal to 1. <b>foo</b> -> 3 // Length of an element which is not mapped is a length of its children. <div><p>x</p><p>y</p></div> -> 2 // Assuming that <div> is not mapped and <p> are mapped.
Parameters
viewNode : Element
View node.
Returns
Number
Length of the node in the tree model.
- if length mapping callback is provided for given
-
markerNameToElements( name ) → Set.<Element> | null
Gets all view elements bound to the given marker name.
Parameters
name : String
Marker name.
Returns
Set.<Element> | null
View elements bound with given marker name or
null
if no elements are bound to given marker name.
-
registerViewToModelLength( viewElementName, lengthCallback )
Registers a callback that evaluates the length in the model of a view element with given name.
The callback is fired with one argument, which is a view element instance. The callback is expected to return a number representing the length of view element in model.
// List item in view may contain nested list, which have other list items. In model though, // the lists are represented by flat structure. Because of those differences, length of list view element // may be greater than one. In the callback it's checked how many nested list items are in evaluated list item. function getViewListItemLength( element ) { let length = 1; for ( let child of element.getChildren() ) { if ( child.name == 'ul' || child.name == 'ol' ) { for ( let item of child.getChildren() ) { length += getViewListItemLength( item ); } } } return length; } mapper.registerViewToModelLength( 'li', getViewListItemLength );
Parameters
viewElementName : String
Name of view element for which callback is registered.
lengthCallback : function
Function return a length of view element instance in model.
-
toModelElement( viewElement ) → Element | undefined
Gets the corresponding model element.
-
toModelPosition( viewPosition ) → Position
-
toModelRange( viewRange ) → Range
Gets the corresponding model range.
-
toViewElement( modelElement ) → Element | undefined
Gets the corresponding view element.
-
toViewPosition( modelPosition, [ options ] = { [options.isPhantom] } ) → Position
Gets the corresponding view position.
Parameters
modelPosition : Position
Model position.
[ options ] : Object
Additional options for position mapping process.
Properties[ options.isPhantom ] : Boolean
Should be set to
true
if the model position to map is pointing to a place in model tree which no longer exists. For example, it could be an end of a removed model range.Defaults to
false
Returns
Position
Corresponding view position.
Fires
-
toViewRange( modelRange ) → Range
Gets the corresponding view range.
-
unbindElementsFromMarkerName( name )
Unbinds all elements from given marker name.
Parameters
name : String
Marker name.
-
unbindModelElement( modelElement )
Unbinds given model element from the map.
Note: model-to-view binding will be removed, if it existed. However, corresponding view-to-model binding will be removed only if view element is still bound to passed
modelElement
.This behavior lets for re-binding view element to another model element without fear of losing the new binding when the previously bound model element is unbound.
Parameters
modelElement : Element
Model element to unbind.
-
unbindViewElement( viewElement )
Unbinds given view element from the map.
Note: view-to-model binding will be removed, if it existed. However, corresponding model-to-view binding will be removed only if model element is still bound to passed
viewElement
.This behavior lets for re-binding model element to another view element without fear of losing the new binding when the previously bound view element is unbound.
Parameters
viewElement : Element
View element to unbind.
-
_findPositionIn( viewParent, expectedOffset ) → Position
private
Finds the position in the view node (or its children) with the expected model offset.
Example:
<p>fo<b>bar</b>bom</p> -> expected offset: 4 _findPositionIn( p, 4 ): <p>|fo<b>bar</b>bom</p> -> expected offset: 4, actual offset: 0 <p>fo|<b>bar</b>bom</p> -> expected offset: 4, actual offset: 2 <p>fo<b>bar</b>|bom</p> -> expected offset: 4, actual offset: 5 -> we are too far _findPositionIn( b, 4 - ( 5 - 3 ) ): <p>fo<b>|bar</b>bom</p> -> expected offset: 2, actual offset: 0 <p>fo<b>bar|</b>bom</p> -> expected offset: 2, actual offset: 3 -> we are too far _findPositionIn( bar, 2 - ( 3 - 3 ) ): We are in the text node so we can simple find the offset. <p>fo<b>ba|r</b>bom</p> -> expected offset: 2, actual offset: 2 -> position found
Parameters
viewParent : Element
Tree view element in which we are looking for the position.
expectedOffset : Number
Expected offset.
Returns
Position
Found position.
-
_moveViewPositionToTextNode( viewPosition ) → Position
private
Because we prefer positions in text nodes over positions next to text node moves view position to the text node if it was next to it.
<p>[]<b>foo</b></p> -> <p>[]<b>foo</b></p> // do not touch if position is not directly next to text <p>foo[]<b>foo</b></p> -> <p>foo{}<b>foo</b></p> // move to text node <p><b>[]foo</b></p> -> <p><b>{}foo</b></p> // move to text node
Parameters
viewPosition : Position
Position potentially next to text node.
Returns
Position
Position in text node if possible.
-
_toModelOffset( viewParent, viewOffset, viewBlock ) → Number
private
Calculates model offset based on the view position and the block element.
Example:
<p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, p ) -> 5
Is a sum of:
<p>foo|<b>bar</b></p> // _toModelOffset( p, 3, p ) -> 3 <p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, b ) -> 2
Parameters
viewParent : Element
Position parent.
viewOffset : Number
Position offset.
viewBlock : Element
Block used as a base to calculate offset.
Returns
Number
Offset in the model.
Events
-
modelToViewPosition( eventInfo, data = { data.mapper } )
Fired for each model-to-view position mapping request. The purpose of this event is to enable custom model-to-view position mapping. Callbacks added to this event take model position and are expected to calculate view position. Calculated view position should be added as
viewPosition
value indata
object that is passed as one of parameters to the event callback.// Assume that "captionedImage" model element is converted to <img> and following <span> elements in view, // and the model element is bound to <img> element. Force mapping model positions inside "captionedImage" to that // <span> element. mapper.on( 'modelToViewPosition', ( evt, data ) => { const positionParent = modelPosition.parent; if ( positionParent.name == 'captionedImage' ) { const viewImg = data.mapper.toViewElement( positionParent ); const viewCaption = viewImg.nextSibling; // The <span> element. data.viewPosition = new ViewPosition( viewCaption, modelPosition.offset ); // Stop the event if other callbacks should not modify calculated value. evt.stop(); } } );
Note: keep in mind that sometimes a "phantom" model position is being converted. "Phantom" model position is a position that points to a non-existing place in model. Such position might still be valid for conversion, though (it would point to a correct place in view when converted). One example of such situation is when a range is removed from model, there may be a need to map the range's end (which is no longer valid model position). To handle such situation, check
data.isPhantom
flag:// Assume that there is "customElement" model element and whenever position is before it, we want to move it // to the inside of the view element bound to "customElement". mapper.on( 'modelToViewPosition', ( evt, data ) => { if ( data.isPhantom ) { return; } // Below line might crash for phantom position that does not exist in model. const sibling = data.modelPosition.nodeBefore; // Check if this is the element we are interested in. if ( !sibling.is( 'customElement' ) ) { return; } const viewElement = data.mapper.toViewElement( sibling ); data.viewPosition = new ViewPosition( sibling, 0 ); evt.stop(); } );
Note: default mapping callback is provided with
low
priority setting and does not cancel the event, so it is possible to attach a custom callback after default callback and also usedata.viewPosition
calculated by default callback (for example to fix it).Note: default mapping callback will not fire if
data.viewPosition
is already set.Note: these callbacks are called very often. For efficiency reasons, it is advised to use them only when position mapping between given model and view elements is unsolvable using just elements mapping and default algorithm. Also, the condition that checks if special case scenario happened should be as simple as possible.
Parameters
eventInfo : EventInfo
An object containing information about the fired event.
data : Object
Data pipeline object that can store and pass data between callbacks. The callback should add
viewPosition
value to that object with calculated view position.Propertiesdata.mapper : Mapper
Mapper instance that fired the event.
-
viewToModelPosition( eventInfo, data = { data.mapper } )
Fired for each view-to-model position mapping request. See
event-modelToViewPosition
.// See example in `modelToViewPosition` event description. // This custom mapping will map positions from <span> element next to <img> to the "captionedImage" element. mapper.on( 'viewToModelPosition', ( evt, data ) => { const positionParent = viewPosition.parent; if ( positionParent.hasClass( 'image-caption' ) ) { const viewImg = positionParent.previousSibling; const modelImg = data.mapper.toModelElement( viewImg ); data.modelPosition = new ModelPosition( modelImg, viewPosition.offset ); evt.stop(); } } );
Note: default mapping callback is provided with
low
priority setting and does not cancel the event, so it is possible to attach a custom callback after default callback and also usedata.modelPosition
calculated by default callback (for example to fix it).Note: default mapping callback will not fire if
data.modelPosition
is already set.Note: these callbacks are called very often. For efficiency reasons, it is advised to use them only when position mapping between given model and view elements is unsolvable using just elements mapping and default algorithm. Also, the condition that checks if special case scenario happened should be as simple as possible.
Parameters
eventInfo : EventInfo
An object containing information about the fired event.
data : Object
Data pipeline object that can store and pass data between callbacks. The callback should add
modelPosition
value to that object with calculated model position.Propertiesdata.mapper : Mapper
Mapper instance that fired the event.