8
0

upcast-converters.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import Matcher from '../view/matcher';
  6. import ModelRange from '../model/range';
  7. import ModelPosition from '../model/position';
  8. import { cloneDeep } from 'lodash-es';
  9. /**
  10. * Contains {@link module:engine/view/view view} to {@link module:engine/model/model model} converters for
  11. * {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher}.
  12. *
  13. * @module engine/conversion/upcast-converters
  14. */
  15. /**
  16. * View element to model element conversion helper.
  17. *
  18. * This conversion results in creating a model element. For example, view `<p>Foo</p>` becomes `<paragraph>Foo</paragraph>` in the model.
  19. *
  20. * Keep in mind that the element will be inserted only if it is allowed by {@link module:engine/model/schema~Schema schema} configuration.
  21. *
  22. * upcastElementToElement( { view: 'p', model: 'paragraph' } );
  23. *
  24. * upcastElementToElement( { view: 'p', model: 'paragraph', converterPriority: 'high' } );
  25. *
  26. * upcastElementToElement( {
  27. * view: {
  28. * name: 'p',
  29. * classes: 'fancy'
  30. * },
  31. * model: 'fancyParagraph'
  32. * } );
  33. *
  34. * upcastElementToElement( {
  35. * view: {
  36. * name: 'p',
  37. * classes: 'heading'
  38. * },
  39. * model: ( viewElement, modelWriter ) => {
  40. * return modelWriter.createElement( 'heading', { level: viewElement.getAttribute( 'data-level' ) } );
  41. * }
  42. * } );
  43. *
  44. * See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add converter to conversion process.
  45. *
  46. * @param {Object} config Conversion configuration.
  47. * @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
  48. * @param {String|module:engine/model/element~Element|Function} config.model Name of the model element, a model element
  49. * instance or a function that takes a view element and returns a model element. The model element will be inserted in the model.
  50. * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
  51. * @returns {Function} Conversion helper.
  52. */
  53. export function upcastElementToElement( config ) {
  54. config = cloneDeep( config );
  55. const converter = _prepareToElementConverter( config );
  56. const elementName = _getViewElementNameFromConfig( config );
  57. const eventName = elementName ? 'element:' + elementName : 'element';
  58. return dispatcher => {
  59. dispatcher.on( eventName, converter, { priority: config.converterPriority || 'normal' } );
  60. };
  61. }
  62. /**
  63. * View element to model attribute conversion helper.
  64. *
  65. * This conversion results in setting an attribute on a model node. For example, view `<strong>Foo</strong>` becomes
  66. * `Foo` {@link module:engine/model/text~Text model text node} with `bold` attribute set to `true`.
  67. *
  68. * This helper is meant to set a model attribute on all the elements that are inside the converted element:
  69. *
  70. * <strong>Foo</strong> --> <strong><p>Foo</p></strong> --> <paragraph><$text bold="true">Foo</$text></paragraph>
  71. *
  72. * Above is a sample of HTML code, that goes through autoparagraphing (first step) and then is converted (second step).
  73. * Even though `<strong>` is over `<p>` element, `bold="true"` was added to the text. See
  74. * {@link module:engine/conversion/upcast-converters~upcastAttributeToAttribute} for comparison.
  75. *
  76. * Keep in mind that the attribute will be set only if it is allowed by {@link module:engine/model/schema~Schema schema} configuration.
  77. *
  78. * upcastElementToAttribute( { view: 'strong', model: 'bold' } );
  79. *
  80. * upcastElementToAttribute( { view: 'strong', model: 'bold', converterPriority: 'high' } );
  81. *
  82. * upcastElementToAttribute( {
  83. * view: {
  84. * name: 'span',
  85. * classes: 'bold'
  86. * },
  87. * model: 'bold'
  88. * } );
  89. *
  90. * upcastElementToAttribute( {
  91. * view: {
  92. * name: 'span',
  93. * classes: [ 'styled', 'styled-dark' ]
  94. * },
  95. * model: {
  96. * key: 'styled',
  97. * value: 'dark'
  98. * }
  99. * } );
  100. *
  101. * upcastElementToAttribute( {
  102. * view: {
  103. * name: 'span',
  104. * styles: {
  105. * 'font-size': /[\s\S]+/
  106. * }
  107. * },
  108. * model: {
  109. * key: 'fontSize',
  110. * value: viewElement => {
  111. * const fontSize = viewElement.getStyle( 'font-size' );
  112. * const value = fontSize.substr( 0, fontSize.length - 2 );
  113. *
  114. * if ( value <= 10 ) {
  115. * return 'small';
  116. * } else if ( value > 12 ) {
  117. * return 'big';
  118. * }
  119. *
  120. * return null;
  121. * }
  122. * }
  123. * } );
  124. *
  125. * See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add converter to conversion process.
  126. *
  127. * @param {Object} config Conversion configuration.
  128. * @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
  129. * @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing
  130. * the model attribute. `value` property may be set as a function that takes a view element and returns the value.
  131. * If `String` is given, the model attribute value will be set to `true`.
  132. * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
  133. * @returns {Function} Conversion helper.
  134. */
  135. export function upcastElementToAttribute( config ) {
  136. config = cloneDeep( config );
  137. _normalizeModelAttributeConfig( config );
  138. const converter = _prepareToAttributeConverter( config, false );
  139. const elementName = _getViewElementNameFromConfig( config );
  140. const eventName = elementName ? 'element:' + elementName : 'element';
  141. return dispatcher => {
  142. dispatcher.on( eventName, converter, { priority: config.converterPriority || 'normal' } );
  143. };
  144. }
  145. /**
  146. * View attribute to model attribute conversion helper.
  147. *
  148. * This conversion results in setting an attribute on a model node. For example, view `<img src="foo.jpg"></img>` becomes
  149. * `<image source="foo.jpg"></image>` in the model.
  150. *
  151. * This helper is meant to convert view attributes from view elements which got converted to the model, so the view attribute
  152. * is set only on the corresponding model node:
  153. *
  154. * <div class="dark"><div>foo</div></div> --> <div dark="true"><div>foo</div></div>
  155. *
  156. * Above, `class="dark"` attribute is added only to the `<div>` elements that has it. This is in contrary to
  157. * {@link module:engine/conversion/upcast-converters~upcastElementToAttribute} which sets attributes for all the children in the model:
  158. *
  159. * <strong>Foo</strong> --> <strong><p>Foo</p></strong> --> <paragraph><$text bold="true">Foo</$text></paragraph>
  160. *
  161. * Above is a sample of HTML code, that goes through autoparagraphing (first step) and then is converted (second step).
  162. * Even though `<strong>` is over `<p>` element, `bold="true"` was added to the text.
  163. *
  164. * Keep in mind that the attribute will be set only if it is allowed by {@link module:engine/model/schema~Schema schema} configuration.
  165. *
  166. * upcastAttributeToAttribute( { view: 'src', model: 'source' } );
  167. *
  168. * upcastAttributeToAttribute( { view: { key: 'src' }, model: 'source' } );
  169. *
  170. * upcastAttributeToAttribute( { view: { key: 'src' }, model: 'source', converterPriority: 'normal' } );
  171. *
  172. * upcastAttributeToAttribute( {
  173. * view: {
  174. * key: 'data-style',
  175. * value: /[\s\S]+/
  176. * },
  177. * model: 'styled'
  178. * } );
  179. *
  180. * upcastAttributeToAttribute( {
  181. * view: {
  182. * name: 'img',
  183. * key: 'class',
  184. * value: 'styled-dark'
  185. * },
  186. * model: {
  187. * key: 'styled',
  188. * value: 'dark'
  189. * }
  190. * } );
  191. *
  192. * upcastAttributeToAttribute( {
  193. * view: {
  194. * key: 'class',
  195. * value: /styled-[\S]+/
  196. * },
  197. * model: {
  198. * key: 'styled'
  199. * value: viewElement => {
  200. * const regexp = /styled-([\S]+)/;
  201. * const match = viewElement.getAttribute( 'class' ).match( regexp );
  202. *
  203. * return match[ 1 ];
  204. * }
  205. * }
  206. * } );
  207. *
  208. * See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add converter to conversion process.
  209. *
  210. * @param {Object} config Conversion configuration.
  211. * @param {String|Object} config.view Specifies which view attribute will be converted. If a `String` is passed,
  212. * attributes with given key will be converted. If an `Object` is passed, it must have a required `key` property,
  213. * specifying view attribute key, and may have an optional `value` property, specifying view attribute value and optional `name`
  214. * property specifying a view element name from/on which the attribute should be converted. `value` can be given as a `String`,
  215. * a `RegExp` or a function callback, that takes view attribute value as the only parameter and returns `Boolean`.
  216. * @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing
  217. * the model attribute. `value` property may be set as a function that takes a view element and returns the value.
  218. * If `String` is given, the model attribute value will be same as view attribute value.
  219. * @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
  220. * @returns {Function} Conversion helper.
  221. */
  222. export function upcastAttributeToAttribute( config ) {
  223. config = cloneDeep( config );
  224. let viewKey = null;
  225. if ( typeof config.view == 'string' || config.view.key ) {
  226. viewKey = _normalizeViewAttributeKeyValueConfig( config );
  227. }
  228. _normalizeModelAttributeConfig( config, viewKey );
  229. const converter = _prepareToAttributeConverter( config, true );
  230. return dispatcher => {
  231. dispatcher.on( 'element', converter, { priority: config.converterPriority || 'low' } );
  232. };
  233. }
  234. /**
  235. * View element to model marker conversion helper.
  236. *
  237. * This conversion results in creating a model marker. For example, if the marker was stored in a view as an element:
  238. * `<p>Fo<span data-marker="comment" data-comment-id="7"></span>o</p><p>B<span data-marker="comment" data-comment-id="7"></span>ar</p>`,
  239. * after the conversion is done, the marker will be available in
  240. * {@link module:engine/model/model~Model#markers model document markers}.
  241. *
  242. * upcastElementToMarker( { view: 'marker-search', model: 'search' } );
  243. *
  244. * upcastElementToMarker( { view: 'marker-search', model: 'search', converterPriority: 'high' } );
  245. *
  246. * upcastElementToMarker( {
  247. * view: 'marker-search',
  248. * model: viewElement => 'comment:' + viewElement.getAttribute( 'data-comment-id' )
  249. * } );
  250. *
  251. * upcastElementToMarker( {
  252. * view: {
  253. * name: 'span',
  254. * attributes: {
  255. * 'data-marker': 'search'
  256. * }
  257. * },
  258. * model: 'search'
  259. * } );
  260. *
  261. * See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add converter to conversion process.
  262. *
  263. * @param {Object} config Conversion configuration.
  264. * @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
  265. * @param {String|Function} config.model Name of the model marker, or a function that takes a view element and returns
  266. * a model marker name.
  267. * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
  268. * @returns {Function} Conversion helper.
  269. */
  270. export function upcastElementToMarker( config ) {
  271. config = cloneDeep( config );
  272. _normalizeToMarkerConfig( config );
  273. return upcastElementToElement( config );
  274. }
  275. // Helper function for from-view-element conversion. Checks if `config.view` directly specifies converted view element's name
  276. // and if so, returns it.
  277. //
  278. // @param {Object} config Conversion config.
  279. // @returns {String|null} View element name or `null` if name is not directly set.
  280. function _getViewElementNameFromConfig( config ) {
  281. if ( typeof config.view == 'string' ) {
  282. return config.view;
  283. }
  284. if ( typeof config.view == 'object' && typeof config.view.name == 'string' ) {
  285. return config.view.name;
  286. }
  287. return null;
  288. }
  289. // Helper for to-model-element conversion. Takes a config object and returns a proper converter function.
  290. //
  291. // @param {Object} config Conversion configuration.
  292. // @returns {Function} View to model converter.
  293. function _prepareToElementConverter( config ) {
  294. const matcher = new Matcher( config.view );
  295. return ( evt, data, conversionApi ) => {
  296. // This will be usually just one pattern but we support matchers with many patterns too.
  297. const match = matcher.match( data.viewItem );
  298. // If there is no match, this callback should not do anything.
  299. if ( !match ) {
  300. return;
  301. }
  302. // Force consuming element's name.
  303. match.match.name = true;
  304. // Create model element basing on config.
  305. const modelElement = _getModelElement( config.model, data.viewItem, conversionApi.writer );
  306. // Do not convert if element building function returned falsy value.
  307. if ( !modelElement ) {
  308. return;
  309. }
  310. // When element was already consumed then skip it.
  311. if ( !conversionApi.consumable.test( data.viewItem, match.match ) ) {
  312. return;
  313. }
  314. // Find allowed parent for element that we are going to insert.
  315. // If current parent does not allow to insert element but one of the ancestors does
  316. // then split nodes to allowed parent.
  317. const splitResult = conversionApi.splitToAllowedParent( modelElement, data.modelCursor );
  318. // When there is no split result it means that we can't insert element to model tree, so let's skip it.
  319. if ( !splitResult ) {
  320. return;
  321. }
  322. // Insert element on allowed position.
  323. conversionApi.writer.insert( modelElement, splitResult.position );
  324. // Convert children and insert to element.
  325. const childrenResult = conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( modelElement ) );
  326. // Consume appropriate value from consumable values list.
  327. conversionApi.consumable.consume( data.viewItem, match.match );
  328. // Set conversion result range.
  329. data.modelRange = new ModelRange(
  330. // Range should start before inserted element
  331. ModelPosition.createBefore( modelElement ),
  332. // Should end after but we need to take into consideration that children could split our
  333. // element, so we need to move range after parent of the last converted child.
  334. // before: <allowed>[]</allowed>
  335. // after: <allowed>[<converted><child></child></converted><child></child><converted>]</converted></allowed>
  336. ModelPosition.createAfter( childrenResult.modelCursor.parent )
  337. );
  338. // Now we need to check where the modelCursor should be.
  339. // If we had to split parent to insert our element then we want to continue conversion inside split parent.
  340. //
  341. // before: <allowed><notAllowed>[]</notAllowed></allowed>
  342. // after: <allowed><notAllowed></notAllowed><converted></converted><notAllowed>[]</notAllowed></allowed>
  343. if ( splitResult.cursorParent ) {
  344. data.modelCursor = ModelPosition.createAt( splitResult.cursorParent );
  345. // Otherwise just continue after inserted element.
  346. } else {
  347. data.modelCursor = data.modelRange.end;
  348. }
  349. };
  350. }
  351. // Helper function for upcasting-to-element converter. Takes the model configuration, the converted view element
  352. // and a writer instance and returns a model element instance to be inserted in the model.
  353. //
  354. // @param {String|Function|module:engine/model/element~Element} model Model conversion configuration.
  355. // @param {module:engine/view/node~Node} input The converted view node.
  356. // @param {module:engine/model/writer~Writer} writer A writer instance to use to create the model element.
  357. function _getModelElement( model, input, writer ) {
  358. if ( model instanceof Function ) {
  359. return model( input, writer );
  360. } else {
  361. return writer.createElement( model );
  362. }
  363. }
  364. // Helper function view-attribute-to-model-attribute helper. Normalizes `config.view` which was set as `String` or
  365. // as an `Object` with `key`, `value` and `name` properties. Normalized `config.view` has is compatible with
  366. // {@link module:engine/view/matcher~MatcherPattern}.
  367. //
  368. // @param {Object} config Conversion config.
  369. // @returns {String} Key of the converted view attribute.
  370. function _normalizeViewAttributeKeyValueConfig( config ) {
  371. if ( typeof config.view == 'string' ) {
  372. config.view = { key: config.view };
  373. }
  374. const key = config.view.key;
  375. let normalized;
  376. if ( key == 'class' || key == 'style' ) {
  377. const keyName = key == 'class' ? 'classes' : 'styles';
  378. normalized = {
  379. [ keyName ]: config.view.value
  380. };
  381. } else {
  382. const value = typeof config.view.value == 'undefined' ? /[\s\S]*/ : config.view.value;
  383. normalized = {
  384. attributes: {
  385. [ key ]: value
  386. }
  387. };
  388. }
  389. if ( config.view.name ) {
  390. normalized.name = config.view.name;
  391. }
  392. config.view = normalized;
  393. return key;
  394. }
  395. // Helper function that normalizes `config.model` in from-model-attribute conversion. `config.model` can be set
  396. // as a `String`, an `Object` with only `key` property or an `Object` with `key` and `value` properties. Normalized
  397. // `config.model` is an `Object` with `key` and `value` properties.
  398. //
  399. // @param {Object} config Conversion config.
  400. // @param {String} viewAttributeKeyToCopy Key of the converted view attribute. If it is set, model attribute value
  401. // will be equal to view attribute value.
  402. function _normalizeModelAttributeConfig( config, viewAttributeKeyToCopy = null ) {
  403. const defaultModelValue = viewAttributeKeyToCopy === null ? true : viewElement => viewElement.getAttribute( viewAttributeKeyToCopy );
  404. const key = typeof config.model != 'object' ? config.model : config.model.key;
  405. const value = typeof config.model != 'object' || typeof config.model.value == 'undefined' ? defaultModelValue : config.model.value;
  406. config.model = { key, value };
  407. }
  408. // Helper for to-model-attribute conversion. Takes the model attribute name and conversion configuration and returns
  409. // a proper converter function.
  410. //
  411. // @param {String} modelAttributeKey The key of the model attribute to set on a model node.
  412. // @param {Object|Array.<Object>} config Conversion configuration. It is possible to provide multiple configurations in an array.
  413. // @param {Boolean} shallow If set to `true` the attribute will be set only on top-level nodes. Otherwise, it will be set
  414. // on all elements in the range.
  415. function _prepareToAttributeConverter( config, shallow ) {
  416. const matcher = new Matcher( config.view );
  417. return ( evt, data, conversionApi ) => {
  418. const match = matcher.match( data.viewItem );
  419. // If there is no match, this callback should not do anything.
  420. if ( !match ) {
  421. return;
  422. }
  423. const modelKey = config.model.key;
  424. const modelValue = typeof config.model.value == 'function' ? config.model.value( data.viewItem ) : config.model.value;
  425. // Do not convert if attribute building function returned falsy value.
  426. if ( modelValue === null ) {
  427. return;
  428. }
  429. if ( _onlyViewNameIsDefined( config ) ) {
  430. match.match.name = true;
  431. } else {
  432. // Do not test or consume `name` consumable.
  433. delete match.match.name;
  434. }
  435. // Try to consume appropriate values from consumable values list.
  436. if ( !conversionApi.consumable.test( data.viewItem, match.match ) ) {
  437. return;
  438. }
  439. // Since we are converting to attribute we need an range on which we will set the attribute.
  440. // If the range is not created yet, we will create it.
  441. if ( !data.modelRange ) {
  442. // Convert children and set conversion result as a current data.
  443. data = Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
  444. }
  445. // Set attribute on current `output`. `Schema` is checked inside this helper function.
  446. const attributeWasSet = _setAttributeOn( data.modelRange, { key: modelKey, value: modelValue }, shallow, conversionApi );
  447. if ( attributeWasSet ) {
  448. conversionApi.consumable.consume( data.viewItem, match.match );
  449. }
  450. };
  451. }
  452. // Helper function that checks if element name should be consumed in attribute converters.
  453. //
  454. // @param {Object} config Conversion config.
  455. // @returns {Boolean}
  456. function _onlyViewNameIsDefined( config ) {
  457. if ( typeof config.view == 'object' && !_getViewElementNameFromConfig( config ) ) {
  458. return false;
  459. }
  460. return !config.view.classes && !config.view.attributes && !config.view.styles;
  461. }
  462. // Helper function for to-model-attribute converter. Sets model attribute on given range. Checks {@link module:engine/model/schema~Schema}
  463. // to ensure proper model structure.
  464. //
  465. // @param {module:engine/model/range~Range} modelRange Model range on which attribute should be set.
  466. // @param {Object} modelAttribute Model attribute to set.
  467. // @param {Object} conversionApi Conversion API.
  468. // @param {Boolean} shallow If set to `true` the attribute will be set only on top-level nodes. Otherwise, it will be set
  469. // on all elements in the range.
  470. // @returns {Boolean} `true` if attribute was set on at least one node from given `modelRange`.
  471. function _setAttributeOn( modelRange, modelAttribute, shallow, conversionApi ) {
  472. let result = false;
  473. // Set attribute on each item in range according to Schema.
  474. for ( const node of Array.from( modelRange.getItems( { shallow } ) ) ) {
  475. if ( conversionApi.schema.checkAttribute( node, modelAttribute.key ) ) {
  476. conversionApi.writer.setAttribute( modelAttribute.key, modelAttribute.value, node );
  477. result = true;
  478. }
  479. }
  480. return result;
  481. }
  482. // Helper function for upcasting-to-marker conversion. Takes the config in a format requested by `upcastElementToMarker()`
  483. // function and converts it to a format that is supported by `upcastElementToElement()` function.
  484. //
  485. // @param {Object} config Conversion configuration.
  486. function _normalizeToMarkerConfig( config ) {
  487. const oldModel = config.model;
  488. config.model = ( viewElement, modelWriter ) => {
  489. const markerName = typeof oldModel == 'string' ? oldModel : oldModel( viewElement );
  490. return modelWriter.createElement( '$marker', { 'data-name': markerName } );
  491. };
  492. }
  493. /**
  494. * Function factory, creates a converter that converts {@link module:engine/view/documentfragment~DocumentFragment view document fragment}
  495. * or all children of {@link module:engine/view/element~Element} into
  496. * {@link module:engine/model/documentfragment~DocumentFragment model document fragment}.
  497. * This is the "entry-point" converter for upcast (view to model conversion). This converter starts the conversion of all children
  498. * of passed view document fragment. Those children {@link module:engine/view/node~Node view nodes} are then handled by other converters.
  499. *
  500. * This also a "default", last resort converter for all view elements that has not been converted by other converters.
  501. * When a view element is being converted to the model but it does not have converter specified, that view element
  502. * will be converted to {@link module:engine/model/documentfragment~DocumentFragment model document fragment} and returned.
  503. *
  504. * @returns {Function} Universal converter for view {@link module:engine/view/documentfragment~DocumentFragment fragments} and
  505. * {@link module:engine/view/element~Element elements} that returns
  506. * {@link module:engine/model/documentfragment~DocumentFragment model fragment} with children of converted view item.
  507. */
  508. export function convertToModelFragment() {
  509. return ( evt, data, conversionApi ) => {
  510. // Second argument in `consumable.consume` is discarded for ViewDocumentFragment but is needed for ViewElement.
  511. if ( !data.modelRange && conversionApi.consumable.consume( data.viewItem, { name: true } ) ) {
  512. const { modelRange, modelCursor } = conversionApi.convertChildren( data.viewItem, data.modelCursor );
  513. data.modelRange = modelRange;
  514. data.modelCursor = modelCursor;
  515. }
  516. };
  517. }
  518. /**
  519. * Function factory, creates a converter that converts {@link module:engine/view/text~Text} to {@link module:engine/model/text~Text}.
  520. *
  521. * @returns {Function} {@link module:engine/view/text~Text View text} converter.
  522. */
  523. export function convertText() {
  524. return ( evt, data, conversionApi ) => {
  525. if ( conversionApi.schema.checkChild( data.modelCursor, '$text' ) ) {
  526. if ( conversionApi.consumable.consume( data.viewItem ) ) {
  527. const text = conversionApi.writer.createText( data.viewItem.data );
  528. conversionApi.writer.insert( text, data.modelCursor );
  529. data.modelRange = ModelRange.createFromPositionAndShift( data.modelCursor, text.offsetSize );
  530. data.modelCursor = data.modelRange.end;
  531. }
  532. }
  533. };
  534. }