definition-based-converters.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module engine/conversion/definition-based-converters
  7. */
  8. import AttributeElement from '../view/attributeelement';
  9. import ViewContainerElement from '../view/containerelement';
  10. import buildModelConverter from './buildmodelconverter';
  11. import buildViewConverter from './buildviewconverter';
  12. /**
  13. * Helper for creating model to view converter from model's element
  14. * to {@link module:engine/view/containerelement~ContainerElement}.
  15. *
  16. * By defining conversion as simple model element to view element conversion using simplified definition:
  17. *
  18. * modelElementToViewContainerElement( {
  19. * model: 'heading1',
  20. * view: 'h1',
  21. * }, [ editor.editing.modelToView, editor.data.modelToView ] );
  22. *
  23. * Or defining full-flavored view object:
  24. *
  25. * modelElementToViewContainerElement( {
  26. * model: 'heading1',
  27. * view: {
  28. * name: 'h1',
  29. * class: [ 'header', 'article-header' ],
  30. * attribute: {
  31. * data-header: 'level-1',
  32. * }
  33. * },
  34. * }, [ editor.editing.modelToView, editor.data.modelToView ] );
  35. *
  36. * Above will generate the following view element:
  37. *
  38. * <h1 class="header article-header" data-header="level-1">...</h1>
  39. *
  40. * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration.
  41. * @param {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher>} dispatchers
  42. */
  43. export function modelElementToViewContainerElement( definition, dispatchers ) {
  44. const { model: modelElement, viewDefinition } = parseConverterDefinition( definition );
  45. buildModelConverter()
  46. .for( ...dispatchers )
  47. .fromElement( modelElement )
  48. .toElement( () => ViewContainerElement.createFromDefinition( viewDefinition ) );
  49. }
  50. /**
  51. * Helper for creating view to model element converter. It will convert also all matched view elements defined in
  52. * `acceptAlso` property. The `model` property is used as model element name.
  53. *
  54. * Conversion from model to view might be defined as simple one to one conversion:
  55. *
  56. * viewToModelElement( { model: 'heading1', view: 'h1' }, [ dispatcher ] );
  57. *
  58. * As a full-flavored definition:
  59. *
  60. * viewToModelElement( {
  61. * model: 'heading1',
  62. * view: {
  63. * name: 'p',
  64. * attribute: {
  65. * 'data-heading': 'true'
  66. * },
  67. * // You may need to use a high-priority listener to catch elements
  68. * // which are handled by other (usually – more generic) converters too.
  69. * priority: 'high'
  70. * }
  71. * }, [ editor.data.viewToModel ] );
  72. *
  73. * or with `acceptAlso` property to match many elements:
  74. *
  75. * viewToModelElement( {
  76. * model: 'heading1',
  77. * view: 'h1',
  78. * acceptAlso: [
  79. * { name: 'p', attribute: { 'data-heading': 'level1' }, priority: 'high' },
  80. * { name: 'h2', class: 'heading-main' },
  81. * { name: 'div', style: { 'font-weight': 'bold', font-size: '24px' } }
  82. * ]
  83. * }, [ editor.data.viewToModel ] );
  84. *
  85. * The above example will convert an existing view elements:
  86. *
  87. * <h1>A heading</h1>
  88. * <h2 class="heading-main">Another heading</h2>
  89. * <p data-heading="level1">Paragraph-like heading</p>
  90. * <div style="font-size:24px; font-weigh:bold;">Another non-semantic header</div>
  91. *
  92. * into `heading1` model elements so in model it will be represented as:
  93. *
  94. * <heading1>A heading</heading1>
  95. * <heading1>Another heading</heading1>
  96. * <heading1>Paragraph-like heading</heading1>
  97. * <heading1>Another non-semantic header</heading1>
  98. *
  99. * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration.
  100. * @param {Array.<module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers
  101. */
  102. export function viewToModelElement( definition, dispatchers ) {
  103. const { model: modelElement, viewDefinitions } = parseConverterDefinition( definition );
  104. const converter = prepareViewConverter( dispatchers, viewDefinitions );
  105. converter.toElement( modelElement );
  106. }
  107. /**
  108. * Helper for creating model to view converter from model's attribute
  109. * to {@link module:engine/view/attributeelement~AttributeElement}.
  110. *
  111. * By defining conversion as simple model element to view element conversion using simplified definition:
  112. *
  113. * modelAttributeToViewAttributeElement( 'bold', {
  114. * model: 'true',
  115. * view: 'strong',
  116. * }, [ editor.editing.modelToView, editor.data.modelToView ] );
  117. *
  118. * Or defining full-flavored view object:
  119. *
  120. * modelAttributeToViewAttributeElement( 'fontSize', {
  121. * model: 'big',
  122. * view: {
  123. * name: 'span',
  124. * style: {
  125. * 'font-size': '1.2em'
  126. * }
  127. * },
  128. * }, [ editor.editing.modelToView, editor.data.modelToView ] );
  129. *
  130. * Above will generate the following view element for model's attribute `fontSize` with a `big` value set:
  131. *
  132. * <span style="font-size:1.2em;">...</span>
  133. *
  134. * @param {String} attributeName The name of the model attribute which should be converted.
  135. * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration.
  136. * @param {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher>} dispatchers
  137. */
  138. export function modelAttributeToViewAttributeElement( attributeName, definition, dispatchers ) {
  139. const { model: attributeValue, viewDefinition } = parseConverterDefinition( definition );
  140. buildModelConverter()
  141. .for( ...dispatchers )
  142. .fromAttribute( attributeName )
  143. .toElement( value => {
  144. if ( value != attributeValue ) {
  145. return;
  146. }
  147. return AttributeElement.createFromDefinition( viewDefinition );
  148. } );
  149. }
  150. /**
  151. * Helper for creating view to model converter from view to model attribute. It will convert also all matched view elements defined in
  152. * `acceptAlso` property. The `model` property is used as model's attribute value to match.
  153. *
  154. * Conversion from model to view might be defined as simple one to one conversion:
  155. *
  156. * viewToModelAttribute( 'bold', { model: true, view: 'strong' }, [ dispatcher ] );
  157. *
  158. * As a full-flavored definition:
  159. *
  160. * viewToModelAttribute( 'fontSize', {
  161. * model: 'big',
  162. * view: {
  163. * name: 'span',
  164. * style: {
  165. * 'font-size': '1.2em'
  166. * }
  167. * }
  168. * }, [ editor.data.viewToModel ] );
  169. *
  170. * or with `acceptAlso` property to match many elements:
  171. *
  172. * viewToModelAttribute( 'fontSize', {
  173. * model: 'big',
  174. * view: {
  175. * name: 'span',
  176. * class: 'text-big'
  177. * },
  178. * acceptAlso: [
  179. * { name: 'span', attribute: { 'data-size': 'big' } },
  180. * { name: 'span', class: [ 'font', 'font-huge' ] },
  181. * { name: 'span', style: { font-size: '18px' } }
  182. * ]
  183. * }, [ editor.data.viewToModel ] );
  184. *
  185. * The above example will convert an existing view elements:
  186. *
  187. * <p>
  188. * An example text with some big elements:
  189. * <span class="text-big>one</span>,
  190. * <span data-size="big>two</span>,
  191. * <span class="font font-huge>three</span>,
  192. * <span style="font-size:18px>four</span>
  193. * </p>
  194. *
  195. * into `fontSize` model attribute with 'big' value set so it will be represented:
  196. *
  197. * <paragraph>
  198. * An example text with some big elements:
  199. * <$text fontSize="big>one</$text>,
  200. * <$text fontSize="big>two</$text>,
  201. * <$text fontSize="big>three</$text>,
  202. * <$text fontSize="big>four</$text>
  203. * </paragraph>
  204. *
  205. * @param {String} attributeName Attribute name to which convert.
  206. * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration.
  207. * @param {Array.<module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers
  208. */
  209. export function viewToModelAttribute( attributeName, definition, dispatchers ) {
  210. const { model: attributeValue, viewDefinitions } = parseConverterDefinition( definition );
  211. const converter = prepareViewConverter( dispatchers, viewDefinitions );
  212. converter.toAttribute( () => ( {
  213. key: attributeName,
  214. value: attributeValue
  215. } ) );
  216. }
  217. // Prepares a {@link module:engine/conversion/definition-based-converters~ConverterDefinition definition object} for building converters.
  218. //
  219. // @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition An object that defines view to model
  220. // and model to view conversion.
  221. // @returns {Object}
  222. function parseConverterDefinition( definition ) {
  223. const model = definition.model;
  224. const view = definition.view;
  225. const viewDefinition = typeof view == 'string' ? { name: view } : view;
  226. const viewDefinitions = Array.from( definition.acceptsAlso ? definition.acceptsAlso : [] );
  227. viewDefinitions.push( viewDefinition );
  228. return { model, viewDefinition, viewDefinitions };
  229. }
  230. // Helper method for preparing a view converter from passed view definitions.
  231. //
  232. // @param {Array.<module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers
  233. // @param {Array.<module:engine/view/viewelementdefinition~ViewElementDefinition>} viewDefinitions
  234. // @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder}
  235. function prepareViewConverter( dispatchers, viewDefinitions ) {
  236. const converter = buildViewConverter().for( ...dispatchers );
  237. for ( const viewDefinition of viewDefinitions ) {
  238. converter.from( Object.assign( {}, viewDefinition ) );
  239. if ( viewDefinition.priority ) {
  240. converter.withPriority( viewDefinition.priority );
  241. }
  242. }
  243. return converter;
  244. }
  245. /**
  246. * Defines conversion details. It is used in configuration based converters:
  247. * - {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement}
  248. * - {@link module:engine/conversion/definition-based-converters~modelElementToViewContainerElement}
  249. * - {@link module:engine/conversion/definition-based-converters~viewToModelAttribute}
  250. * - {@link module:engine/conversion/definition-based-converters~viewToModelElement}
  251. *
  252. * @typedef {Object} ConverterDefinition
  253. * @property {String} model Defines to model conversion. When using to element conversion
  254. * ({@link module:engine/conversion/definition-based-converters~viewToModelElement}
  255. * and {@link module:engine/conversion/definition-based-converters~modelElementToViewContainerElement})
  256. * it defines element name. When using to attribute conversion
  257. * ({@link module:engine/conversion/definition-based-converters~viewToModelAttribute}
  258. * and {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement})
  259. * it defines attribute value to which it is converted.
  260. * @property {module:engine/view/viewelementdefinition~ViewElementDefinition} view Defines model to view conversion and is also used
  261. * in view to model conversion pipeline.
  262. * @property {Array.<module:engine/view/viewelementdefinition~ViewElementDefinition>} acceptAlso An array with all matched elements that
  263. * view to model conversion should also accepts.
  264. */