@ckeditor/ckeditor5-engine 47.6.1 → 48.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/LICENSE.md +1 -1
  2. package/{src → dist}/engineconfig.d.ts +6 -15
  3. package/dist/index-editor.css +38 -15
  4. package/dist/index.css +37 -37
  5. package/dist/index.css.map +1 -1
  6. package/{src → dist}/index.d.ts +0 -1
  7. package/dist/index.js +588 -94
  8. package/dist/index.js.map +1 -1
  9. package/{src → dist}/model/model.d.ts +10 -4
  10. package/{src → dist}/model/selection.d.ts +1 -1
  11. package/{src → dist}/view/downcastwriter.d.ts +3 -2
  12. package/{src → dist}/view/element.d.ts +2 -2
  13. package/{src → dist}/view/matcher.d.ts +4 -2
  14. package/dist/view/styles/background.d.ts +18 -0
  15. package/{src → dist}/view/styles/border.d.ts +0 -12
  16. package/{src → dist}/view/styles/margin.d.ts +0 -13
  17. package/{src → dist}/view/styles/padding.d.ts +0 -13
  18. package/{src → dist}/view/styles/utils.d.ts +12 -0
  19. package/package.json +20 -39
  20. package/src/controller/datacontroller.js +0 -522
  21. package/src/controller/editingcontroller.js +0 -181
  22. package/src/conversion/conversion.js +0 -606
  23. package/src/conversion/conversionhelpers.js +0 -33
  24. package/src/conversion/downcastdispatcher.js +0 -563
  25. package/src/conversion/downcasthelpers.js +0 -2160
  26. package/src/conversion/mapper.js +0 -1050
  27. package/src/conversion/modelconsumable.js +0 -331
  28. package/src/conversion/upcastdispatcher.js +0 -470
  29. package/src/conversion/upcasthelpers.js +0 -952
  30. package/src/conversion/viewconsumable.js +0 -541
  31. package/src/dataprocessor/basichtmlwriter.js +0 -22
  32. package/src/dataprocessor/dataprocessor.js +0 -5
  33. package/src/dataprocessor/htmldataprocessor.js +0 -107
  34. package/src/dataprocessor/htmlwriter.js +0 -5
  35. package/src/dataprocessor/xmldataprocessor.js +0 -127
  36. package/src/dev-utils/model.js +0 -396
  37. package/src/dev-utils/operationreplayer.js +0 -116
  38. package/src/dev-utils/utils.js +0 -122
  39. package/src/dev-utils/view.js +0 -990
  40. package/src/engineconfig.js +0 -5
  41. package/src/index.js +0 -134
  42. package/src/legacyerrors.js +0 -17
  43. package/src/model/batch.js +0 -98
  44. package/src/model/differ.js +0 -1288
  45. package/src/model/document.js +0 -398
  46. package/src/model/documentfragment.js +0 -332
  47. package/src/model/documentselection.js +0 -1026
  48. package/src/model/element.js +0 -323
  49. package/src/model/history.js +0 -206
  50. package/src/model/item.js +0 -5
  51. package/src/model/liveposition.js +0 -93
  52. package/src/model/liverange.js +0 -121
  53. package/src/model/markercollection.js +0 -436
  54. package/src/model/model.js +0 -866
  55. package/src/model/node.js +0 -371
  56. package/src/model/nodelist.js +0 -244
  57. package/src/model/operation/attributeoperation.js +0 -172
  58. package/src/model/operation/detachoperation.js +0 -87
  59. package/src/model/operation/insertoperation.js +0 -153
  60. package/src/model/operation/markeroperation.js +0 -136
  61. package/src/model/operation/mergeoperation.js +0 -184
  62. package/src/model/operation/moveoperation.js +0 -179
  63. package/src/model/operation/nooperation.js +0 -48
  64. package/src/model/operation/operation.js +0 -78
  65. package/src/model/operation/operationfactory.js +0 -44
  66. package/src/model/operation/renameoperation.js +0 -128
  67. package/src/model/operation/rootattributeoperation.js +0 -173
  68. package/src/model/operation/rootoperation.js +0 -106
  69. package/src/model/operation/splitoperation.js +0 -214
  70. package/src/model/operation/transform.js +0 -2211
  71. package/src/model/operation/utils.js +0 -217
  72. package/src/model/position.js +0 -1041
  73. package/src/model/range.js +0 -880
  74. package/src/model/rootelement.js +0 -82
  75. package/src/model/schema.js +0 -1542
  76. package/src/model/selection.js +0 -814
  77. package/src/model/text.js +0 -92
  78. package/src/model/textproxy.js +0 -202
  79. package/src/model/treewalker.js +0 -313
  80. package/src/model/typecheckable.js +0 -16
  81. package/src/model/utils/autoparagraphing.js +0 -63
  82. package/src/model/utils/deletecontent.js +0 -509
  83. package/src/model/utils/getselectedcontent.js +0 -126
  84. package/src/model/utils/insertcontent.js +0 -750
  85. package/src/model/utils/insertobject.js +0 -135
  86. package/src/model/utils/modifyselection.js +0 -187
  87. package/src/model/utils/selection-post-fixer.js +0 -264
  88. package/src/model/writer.js +0 -1318
  89. package/src/view/attributeelement.js +0 -220
  90. package/src/view/containerelement.js +0 -91
  91. package/src/view/datatransfer.js +0 -106
  92. package/src/view/document.js +0 -139
  93. package/src/view/documentfragment.js +0 -251
  94. package/src/view/documentselection.js +0 -270
  95. package/src/view/domconverter.js +0 -1661
  96. package/src/view/downcastwriter.js +0 -1589
  97. package/src/view/editableelement.js +0 -74
  98. package/src/view/element.js +0 -1053
  99. package/src/view/elementdefinition.js +0 -5
  100. package/src/view/emptyelement.js +0 -83
  101. package/src/view/filler.js +0 -161
  102. package/src/view/item.js +0 -5
  103. package/src/view/matcher.js +0 -437
  104. package/src/view/node.js +0 -238
  105. package/src/view/observer/arrowkeysobserver.js +0 -40
  106. package/src/view/observer/bubblingemittermixin.js +0 -215
  107. package/src/view/observer/bubblingeventinfo.js +0 -49
  108. package/src/view/observer/clickobserver.js +0 -26
  109. package/src/view/observer/compositionobserver.js +0 -64
  110. package/src/view/observer/domeventdata.js +0 -63
  111. package/src/view/observer/domeventobserver.js +0 -81
  112. package/src/view/observer/fakeselectionobserver.js +0 -95
  113. package/src/view/observer/focusobserver.js +0 -166
  114. package/src/view/observer/inputobserver.js +0 -236
  115. package/src/view/observer/keyobserver.js +0 -36
  116. package/src/view/observer/mouseobserver.js +0 -26
  117. package/src/view/observer/mutationobserver.js +0 -219
  118. package/src/view/observer/observer.js +0 -92
  119. package/src/view/observer/pointerobserver.js +0 -26
  120. package/src/view/observer/selectionobserver.js +0 -318
  121. package/src/view/observer/tabobserver.js +0 -42
  122. package/src/view/observer/touchobserver.js +0 -26
  123. package/src/view/placeholder.js +0 -285
  124. package/src/view/position.js +0 -341
  125. package/src/view/range.js +0 -451
  126. package/src/view/rawelement.js +0 -115
  127. package/src/view/renderer.js +0 -1148
  128. package/src/view/rooteditableelement.js +0 -78
  129. package/src/view/selection.js +0 -594
  130. package/src/view/styles/background.d.ts +0 -33
  131. package/src/view/styles/background.js +0 -74
  132. package/src/view/styles/border.js +0 -316
  133. package/src/view/styles/margin.js +0 -34
  134. package/src/view/styles/padding.js +0 -34
  135. package/src/view/styles/utils.js +0 -219
  136. package/src/view/stylesmap.js +0 -941
  137. package/src/view/text.js +0 -110
  138. package/src/view/textproxy.js +0 -136
  139. package/src/view/tokenlist.js +0 -194
  140. package/src/view/treewalker.js +0 -389
  141. package/src/view/typecheckable.js +0 -19
  142. package/src/view/uielement.js +0 -194
  143. package/src/view/upcastwriter.js +0 -363
  144. package/src/view/view.js +0 -579
  145. package/theme/placeholder.css +0 -36
  146. package/theme/renderer.css +0 -9
  147. /package/{src → dist}/controller/datacontroller.d.ts +0 -0
  148. /package/{src → dist}/controller/editingcontroller.d.ts +0 -0
  149. /package/{src → dist}/conversion/conversion.d.ts +0 -0
  150. /package/{src → dist}/conversion/conversionhelpers.d.ts +0 -0
  151. /package/{src → dist}/conversion/downcastdispatcher.d.ts +0 -0
  152. /package/{src → dist}/conversion/downcasthelpers.d.ts +0 -0
  153. /package/{src → dist}/conversion/mapper.d.ts +0 -0
  154. /package/{src → dist}/conversion/modelconsumable.d.ts +0 -0
  155. /package/{src → dist}/conversion/upcastdispatcher.d.ts +0 -0
  156. /package/{src → dist}/conversion/upcasthelpers.d.ts +0 -0
  157. /package/{src → dist}/conversion/viewconsumable.d.ts +0 -0
  158. /package/{src → dist}/dataprocessor/basichtmlwriter.d.ts +0 -0
  159. /package/{src → dist}/dataprocessor/dataprocessor.d.ts +0 -0
  160. /package/{src → dist}/dataprocessor/htmldataprocessor.d.ts +0 -0
  161. /package/{src → dist}/dataprocessor/htmlwriter.d.ts +0 -0
  162. /package/{src → dist}/dataprocessor/xmldataprocessor.d.ts +0 -0
  163. /package/{src → dist}/dev-utils/model.d.ts +0 -0
  164. /package/{src → dist}/dev-utils/operationreplayer.d.ts +0 -0
  165. /package/{src → dist}/dev-utils/utils.d.ts +0 -0
  166. /package/{src → dist}/dev-utils/view.d.ts +0 -0
  167. /package/{src → dist}/legacyerrors.d.ts +0 -0
  168. /package/{src → dist}/model/batch.d.ts +0 -0
  169. /package/{src → dist}/model/differ.d.ts +0 -0
  170. /package/{src → dist}/model/document.d.ts +0 -0
  171. /package/{src → dist}/model/documentfragment.d.ts +0 -0
  172. /package/{src → dist}/model/documentselection.d.ts +0 -0
  173. /package/{src → dist}/model/element.d.ts +0 -0
  174. /package/{src → dist}/model/history.d.ts +0 -0
  175. /package/{src → dist}/model/item.d.ts +0 -0
  176. /package/{src → dist}/model/liveposition.d.ts +0 -0
  177. /package/{src → dist}/model/liverange.d.ts +0 -0
  178. /package/{src → dist}/model/markercollection.d.ts +0 -0
  179. /package/{src → dist}/model/node.d.ts +0 -0
  180. /package/{src → dist}/model/nodelist.d.ts +0 -0
  181. /package/{src → dist}/model/operation/attributeoperation.d.ts +0 -0
  182. /package/{src → dist}/model/operation/detachoperation.d.ts +0 -0
  183. /package/{src → dist}/model/operation/insertoperation.d.ts +0 -0
  184. /package/{src → dist}/model/operation/markeroperation.d.ts +0 -0
  185. /package/{src → dist}/model/operation/mergeoperation.d.ts +0 -0
  186. /package/{src → dist}/model/operation/moveoperation.d.ts +0 -0
  187. /package/{src → dist}/model/operation/nooperation.d.ts +0 -0
  188. /package/{src → dist}/model/operation/operation.d.ts +0 -0
  189. /package/{src → dist}/model/operation/operationfactory.d.ts +0 -0
  190. /package/{src → dist}/model/operation/renameoperation.d.ts +0 -0
  191. /package/{src → dist}/model/operation/rootattributeoperation.d.ts +0 -0
  192. /package/{src → dist}/model/operation/rootoperation.d.ts +0 -0
  193. /package/{src → dist}/model/operation/splitoperation.d.ts +0 -0
  194. /package/{src → dist}/model/operation/transform.d.ts +0 -0
  195. /package/{src → dist}/model/operation/utils.d.ts +0 -0
  196. /package/{src → dist}/model/position.d.ts +0 -0
  197. /package/{src → dist}/model/range.d.ts +0 -0
  198. /package/{src → dist}/model/rootelement.d.ts +0 -0
  199. /package/{src → dist}/model/schema.d.ts +0 -0
  200. /package/{src → dist}/model/text.d.ts +0 -0
  201. /package/{src → dist}/model/textproxy.d.ts +0 -0
  202. /package/{src → dist}/model/treewalker.d.ts +0 -0
  203. /package/{src → dist}/model/typecheckable.d.ts +0 -0
  204. /package/{src → dist}/model/utils/autoparagraphing.d.ts +0 -0
  205. /package/{src → dist}/model/utils/deletecontent.d.ts +0 -0
  206. /package/{src → dist}/model/utils/getselectedcontent.d.ts +0 -0
  207. /package/{src → dist}/model/utils/insertcontent.d.ts +0 -0
  208. /package/{src → dist}/model/utils/insertobject.d.ts +0 -0
  209. /package/{src → dist}/model/utils/modifyselection.d.ts +0 -0
  210. /package/{src → dist}/model/utils/selection-post-fixer.d.ts +0 -0
  211. /package/{src → dist}/model/writer.d.ts +0 -0
  212. /package/{src → dist}/view/attributeelement.d.ts +0 -0
  213. /package/{src → dist}/view/containerelement.d.ts +0 -0
  214. /package/{src → dist}/view/datatransfer.d.ts +0 -0
  215. /package/{src → dist}/view/document.d.ts +0 -0
  216. /package/{src → dist}/view/documentfragment.d.ts +0 -0
  217. /package/{src → dist}/view/documentselection.d.ts +0 -0
  218. /package/{src → dist}/view/domconverter.d.ts +0 -0
  219. /package/{src → dist}/view/editableelement.d.ts +0 -0
  220. /package/{src → dist}/view/elementdefinition.d.ts +0 -0
  221. /package/{src → dist}/view/emptyelement.d.ts +0 -0
  222. /package/{src → dist}/view/filler.d.ts +0 -0
  223. /package/{src → dist}/view/item.d.ts +0 -0
  224. /package/{src → dist}/view/node.d.ts +0 -0
  225. /package/{src → dist}/view/observer/arrowkeysobserver.d.ts +0 -0
  226. /package/{src → dist}/view/observer/bubblingemittermixin.d.ts +0 -0
  227. /package/{src → dist}/view/observer/bubblingeventinfo.d.ts +0 -0
  228. /package/{src → dist}/view/observer/clickobserver.d.ts +0 -0
  229. /package/{src → dist}/view/observer/compositionobserver.d.ts +0 -0
  230. /package/{src → dist}/view/observer/domeventdata.d.ts +0 -0
  231. /package/{src → dist}/view/observer/domeventobserver.d.ts +0 -0
  232. /package/{src → dist}/view/observer/fakeselectionobserver.d.ts +0 -0
  233. /package/{src → dist}/view/observer/focusobserver.d.ts +0 -0
  234. /package/{src → dist}/view/observer/inputobserver.d.ts +0 -0
  235. /package/{src → dist}/view/observer/keyobserver.d.ts +0 -0
  236. /package/{src → dist}/view/observer/mouseobserver.d.ts +0 -0
  237. /package/{src → dist}/view/observer/mutationobserver.d.ts +0 -0
  238. /package/{src → dist}/view/observer/observer.d.ts +0 -0
  239. /package/{src → dist}/view/observer/pointerobserver.d.ts +0 -0
  240. /package/{src → dist}/view/observer/selectionobserver.d.ts +0 -0
  241. /package/{src → dist}/view/observer/tabobserver.d.ts +0 -0
  242. /package/{src → dist}/view/observer/touchobserver.d.ts +0 -0
  243. /package/{src → dist}/view/placeholder.d.ts +0 -0
  244. /package/{src → dist}/view/position.d.ts +0 -0
  245. /package/{src → dist}/view/range.d.ts +0 -0
  246. /package/{src → dist}/view/rawelement.d.ts +0 -0
  247. /package/{src → dist}/view/renderer.d.ts +0 -0
  248. /package/{src → dist}/view/rooteditableelement.d.ts +0 -0
  249. /package/{src → dist}/view/selection.d.ts +0 -0
  250. /package/{src → dist}/view/stylesmap.d.ts +0 -0
  251. /package/{src → dist}/view/text.d.ts +0 -0
  252. /package/{src → dist}/view/textproxy.d.ts +0 -0
  253. /package/{src → dist}/view/tokenlist.d.ts +0 -0
  254. /package/{src → dist}/view/treewalker.d.ts +0 -0
  255. /package/{src → dist}/view/typecheckable.d.ts +0 -0
  256. /package/{src → dist}/view/uielement.d.ts +0 -0
  257. /package/{src → dist}/view/upcastwriter.d.ts +0 -0
  258. /package/{src → dist}/view/view.d.ts +0 -0
@@ -1,990 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
- */
5
- /**
6
- * @module engine/dev-utils/view
7
- */
8
- /**
9
- * Collection of methods for manipulating the {@link module:engine/view/view view} for testing purposes.
10
- */
11
- import { EditingView } from '../view/view.js';
12
- import { ViewDocument } from '../view/document.js';
13
- import { ViewDocumentFragment } from '../view/documentfragment.js';
14
- import { XmlDataProcessor } from '../dataprocessor/xmldataprocessor.js';
15
- import { ViewElement } from '../view/element.js';
16
- import { ViewDocumentSelection } from '../view/documentselection.js';
17
- import { ViewRange } from '../view/range.js';
18
- import { ViewPosition } from '../view/position.js';
19
- import { ViewAttributeElement } from '../view/attributeelement.js';
20
- import { ViewContainerElement } from '../view/containerelement.js';
21
- import { ViewEmptyElement } from '../view/emptyelement.js';
22
- import { ViewUIElement } from '../view/uielement.js';
23
- import { ViewRawElement } from '../view/rawelement.js';
24
- import { StylesProcessor } from '../view/stylesmap.js';
25
- const ELEMENT_RANGE_START_TOKEN = '[';
26
- const ELEMENT_RANGE_END_TOKEN = ']';
27
- const TEXT_RANGE_START_TOKEN = '{';
28
- const TEXT_RANGE_END_TOKEN = '}';
29
- const allowedTypes = {
30
- 'container': ViewContainerElement,
31
- 'attribute': ViewAttributeElement,
32
- 'empty': ViewEmptyElement,
33
- 'ui': ViewUIElement,
34
- 'raw': ViewRawElement
35
- };
36
- // Returns simplified implementation of {@link module:engine/view/domconverter~ViewDomConverter#setContentOf ViewDomConverter.setContentOf}
37
- // method. Used to render UIElement and RawElement.
38
- const domConverterStub = {
39
- setContentOf: (node, html) => {
40
- node.innerHTML = html;
41
- }
42
- };
43
- /**
44
- * Writes the content of the {@link module:engine/view/document~ViewDocument document} to an HTML-like string.
45
- *
46
- * @param view The view to stringify.
47
- * @param options.withoutSelection Whether to write the selection. When set to `true`, the selection will
48
- * not be included in the returned string.
49
- * @param options.rootName The name of the root from which the data should be stringified. If not provided,
50
- * the default `main` name will be used.
51
- * @param options.showType When set to `true`, the type of elements will be printed (`<container:p>`
52
- * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
53
- * @param options.showPriority When set to `true`, the attribute element's priority will be printed
54
- * (`<span view-priority="12">`, `<b view-priority="10">`).
55
- * @param options.renderUIElements When set to `true`, the inner content of each
56
- * {@link module:engine/view/uielement~ViewUIElement} will be printed.
57
- * @param options.renderRawElements When set to `true`, the inner content of each
58
- * {@link module:engine/view/rawelement~ViewRawElement} will be printed.
59
- * @param options.domConverter When set to an actual {@link module:engine/view/domconverter~ViewDomConverter ViewDomConverter}
60
- * instance, it lets the conversion go through exactly the same flow the editing view is going through,
61
- * i.e. with view data filtering. Otherwise the simple stub is used.
62
- * @returns The stringified data.
63
- */
64
- export function _getViewData(view, options = {}) {
65
- if (!(view instanceof EditingView)) {
66
- throw new TypeError('View needs to be an instance of module:engine/view/view~EditingView.');
67
- }
68
- const document = view.document;
69
- const withoutSelection = !!options.withoutSelection;
70
- const rootName = options.rootName || 'main';
71
- const root = document.getRoot(rootName);
72
- const stringifyOptions = {
73
- showType: options.showType,
74
- showPriority: options.showPriority,
75
- renderUIElements: options.renderUIElements,
76
- renderRawElements: options.renderRawElements,
77
- ignoreRoot: true,
78
- domConverter: options.domConverter,
79
- skipListItemIds: options.skipListItemIds
80
- };
81
- return withoutSelection ?
82
- _getViewData._stringify(root, null, stringifyOptions) :
83
- _getViewData._stringify(root, document.selection, stringifyOptions);
84
- }
85
- // Set stringify as getData private method - needed for testing/spying.
86
- _getViewData._stringify = _stringifyView;
87
- /**
88
- * Sets the content of a view {@link module:engine/view/document~ViewDocument document} provided as an HTML-like string.
89
- *
90
- * @param data An HTML-like string to write into the document.
91
- * @param options.rootName The root name where _parseViewd data will be stored. If not provided,
92
- * the default `main` name will be used.
93
- */
94
- export function _setViewData(view, data, options = {}) {
95
- if (!(view instanceof EditingView)) {
96
- throw new TypeError('View needs to be an instance of module:engine/view/view~EditingView.');
97
- }
98
- const document = view.document;
99
- const rootName = options.rootName || 'main';
100
- const root = document.getRoot(rootName);
101
- view.change(writer => {
102
- const result = _setViewData._parse(data, { rootElement: root });
103
- if (result.view && result.selection) {
104
- writer.setSelection(result.selection);
105
- }
106
- });
107
- }
108
- // Set _parseView as _setViewData private method - needed for testing/spying.
109
- _setViewData._parse = _parseView;
110
- /**
111
- * Converts view elements to HTML-like string representation.
112
- *
113
- * A root element can be provided as {@link module:engine/view/text~ViewText text}:
114
- *
115
- * ```ts
116
- * const text = downcastWriter.createText( 'foobar' );
117
- * stringify( text ); // 'foobar'
118
- * ```
119
- *
120
- * or as an {@link module:engine/view/element~ViewElement element}:
121
- *
122
- * ```ts
123
- * const element = downcastWriter.createElement( 'p', null, downcastWriter.createText( 'foobar' ) );
124
- * stringify( element ); // '<p>foobar</p>'
125
- * ```
126
- *
127
- * or as a {@link module:engine/view/documentfragment~ViewDocumentFragment document fragment}:
128
- *
129
- * ```ts
130
- * const text = downcastWriter.createText( 'foobar' );
131
- * const b = downcastWriter.createElement( 'b', { name: 'test' }, text );
132
- * const p = downcastWriter.createElement( 'p', { style: 'color:red;' } );
133
- * const fragment = downcastWriter.createDocumentFragment( [ p, b ] );
134
- *
135
- * stringify( fragment ); // '<p style="color:red;"></p><b name="test">foobar</b>'
136
- * ```
137
- *
138
- * Additionally, a {@link module:engine/view/documentselection~ViewDocumentSelection selection} instance can be provided.
139
- * Ranges from the selection will then be included in the output data.
140
- * If a range position is placed inside the element node, it will be represented with `[` and `]`:
141
- *
142
- * ```ts
143
- * const text = downcastWriter.createText( 'foobar' );
144
- * const b = downcastWriter.createElement( 'b', null, text );
145
- * const p = downcastWriter.createElement( 'p', null, b );
146
- * const selection = downcastWriter.createSelection(
147
- * downcastWriter.createRangeIn( p )
148
- * );
149
- *
150
- * stringify( p, selection ); // '<p>[<b>foobar</b>]</p>'
151
- * ```
152
- *
153
- * If a range is placed inside the text node, it will be represented with `{` and `}`:
154
- *
155
- * ```ts
156
- * const text = downcastWriter.createText( 'foobar' );
157
- * const b = downcastWriter.createElement( 'b', null, text );
158
- * const p = downcastWriter.createElement( 'p', null, b );
159
- * const selection = downcastWriter.createSelection(
160
- * downcastWriter.createRange( downcastWriter.createPositionAt( text, 1 ), downcastWriter.createPositionAt( text, 5 ) )
161
- * );
162
- *
163
- * stringify( p, selection ); // '<p><b>f{ooba}r</b></p>'
164
- * ```
165
- *
166
- * ** Note: **
167
- * It is possible to unify selection markers to `[` and `]` for both (inside and outside text)
168
- * by setting the `sameSelectionCharacters=true` option. It is mainly used when the view stringify option is used by
169
- * model utilities.
170
- *
171
- * Multiple ranges are supported:
172
- *
173
- * ```ts
174
- * const text = downcastWriter.createText( 'foobar' );
175
- * const selection = downcastWriter.createSelection( [
176
- * downcastWriter.createRange( downcastWriter.createPositionAt( text, 0 ), downcastWriter.createPositionAt( text, 1 ) ),
177
- * downcastWriter.createRange( downcastWriter.createPositionAt( text, 3 ), downcastWriter.createPositionAt( text, 5 ) )
178
- * ] );
179
- *
180
- * stringify( text, selection ); // '{f}oo{ba}r'
181
- * ```
182
- *
183
- * A {@link module:engine/view/range~ViewRange range} or {@link module:engine/view/position~ViewPosition position} instance can be provided
184
- * instead of the {@link module:engine/view/documentselection~ViewDocumentSelection selection} instance. If a range instance
185
- * is provided, it will be converted to a selection containing this range. If a position instance is provided, it will
186
- * be converted to a selection containing one range collapsed at this position.
187
- *
188
- * ```ts
189
- * const text = downcastWriter.createText( 'foobar' );
190
- * const range = downcastWriter.createRange( downcastWriter.createPositionAt( text, 0 ), downcastWriter.createPositionAt( text, 1 ) );
191
- * const position = downcastWriter.createPositionAt( text, 3 );
192
- *
193
- * stringify( text, range ); // '{f}oobar'
194
- * stringify( text, position ); // 'foo{}bar'
195
- * ```
196
- *
197
- * An additional `options` object can be provided.
198
- * If `options.showType` is set to `true`, element's types will be
199
- * presented for {@link module:engine/view/attributeelement~ViewAttributeElement attribute elements},
200
- * {@link module:engine/view/containerelement~ViewContainerElement container elements}
201
- * {@link module:engine/view/emptyelement~ViewEmptyElement empty elements}
202
- * and {@link module:engine/view/uielement~ViewUIElement UI elements}:
203
- *
204
- * ```ts
205
- * const attribute = downcastWriter.createAttributeElement( 'b' );
206
- * const container = downcastWriter.createContainerElement( 'p' );
207
- * const empty = downcastWriter.createEmptyElement( 'img' );
208
- * const ui = downcastWriter.createUIElement( 'span' );
209
- * getData( attribute, null, { showType: true } ); // '<attribute:b></attribute:b>'
210
- * getData( container, null, { showType: true } ); // '<container:p></container:p>'
211
- * getData( empty, null, { showType: true } ); // '<empty:img></empty:img>'
212
- * getData( ui, null, { showType: true } ); // '<ui:span></ui:span>'
213
- * ```
214
- *
215
- * If `options.showPriority` is set to `true`, a priority will be displayed for all
216
- * {@link module:engine/view/attributeelement~ViewAttributeElement attribute elements}.
217
- *
218
- * ```ts
219
- * const attribute = downcastWriter.createAttributeElement( 'b' );
220
- * attribute._priority = 20;
221
- * getData( attribute, null, { showPriority: true } ); // <b view-priority="20"></b>
222
- * ```
223
- *
224
- * If `options.showAttributeElementId` is set to `true`, the attribute element's id will be displayed for all
225
- * {@link module:engine/view/attributeelement~ViewAttributeElement attribute elements} that have it set.
226
- *
227
- * ```ts
228
- * const attribute = downcastWriter.createAttributeElement( 'span' );
229
- * attribute._id = 'marker:foo';
230
- * getData( attribute, null, { showAttributeElementId: true } ); // <span view-id="marker:foo"></span>
231
- * ```
232
- *
233
- * @param node The node to stringify.
234
- * @param selectionOrPositionOrRange A selection instance whose ranges will be included in the returned string data.
235
- * If a range instance is provided, it will be converted to a selection containing this range. If a position instance
236
- * is provided, it will be converted to a selection containing one range collapsed at this position.
237
- * @param options An object with additional options.
238
- * @param options.showType When set to `true`, the type of elements will be printed (`<container:p>`
239
- * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
240
- * @param options.showPriority When set to `true`, the attribute element's priority will be printed
241
- * (`<span view-priority="12">`, `<b view-priority="10">`).
242
- * @param options.showAttributeElementId When set to `true`, attribute element's id will be printed
243
- * (`<span id="marker:foo">`).
244
- * @param options.ignoreRoot When set to `true`, the root's element opening and closing will not be printed.
245
- * Mainly used by the `getData` function to ignore the {@link module:engine/view/document~ViewDocument document's} root element.
246
- * @param options.sameSelectionCharacters When set to `true`, the selection inside the text will be marked as
247
- * `{` and `}` and the selection outside the text as `[` and `]`. When set to `false`, both will be marked as `[` and `]` only.
248
- * @param options.renderUIElements When set to `true`, the inner content of each
249
- * {@link module:engine/view/uielement~ViewUIElement} will be printed.
250
- * @param options.renderRawElements When set to `true`, the inner content of each
251
- * {@link module:engine/view/rawelement~ViewRawElement} will be printed.
252
- * @param options.domConverter When set to an actual {@link module:engine/view/domconverter~ViewDomConverter ViewDomConverter}
253
- * instance, it lets the conversion go through exactly the same flow the editing view is going through,
254
- * i.e. with view data filtering. Otherwise the simple stub is used.
255
- * @returns An HTML-like string representing the view.
256
- */
257
- export function _stringifyView(node, selectionOrPositionOrRange = null, options = {}) {
258
- let selection;
259
- if (selectionOrPositionOrRange instanceof ViewPosition ||
260
- selectionOrPositionOrRange instanceof ViewRange) {
261
- selection = new ViewDocumentSelection(selectionOrPositionOrRange);
262
- }
263
- else {
264
- selection = selectionOrPositionOrRange;
265
- }
266
- const viewStringify = new ViewStringify(node, selection, options);
267
- return viewStringify.stringify();
268
- }
269
- /**
270
- * Parses an HTML-like string and returns a view tree.
271
- * A simple string will be converted to a {@link module:engine/view/text~ViewText text} node:
272
- *
273
- * ```ts
274
- * _parseView( 'foobar' ); // Returns an instance of text.
275
- * ```
276
- *
277
- * {@link module:engine/view/element~ViewElement Elements} will be _parseViewd with attributes as children:
278
- *
279
- * ```ts
280
- * _parseView( '<b name="baz">foobar</b>' ); // Returns an instance of element with the `baz` attribute and a text child node.
281
- * ```
282
- *
283
- * Multiple nodes provided on root level will be converted to a
284
- * {@link module:engine/view/documentfragment~ViewDocumentFragment document fragment}:
285
- *
286
- * ```ts
287
- * _parseView( '<b>foo</b><i>bar</i>' ); // Returns a document fragment with two child elements.
288
- * ```
289
- *
290
- * The method can _parseView multiple {@link module:engine/view/range~ViewRange ranges} provided in string data and return a
291
- * {@link module:engine/view/documentselection~ViewDocumentSelection selection} instance containing these ranges. Ranges placed inside
292
- * {@link module:engine/view/text~ViewText text} nodes should be marked using `{` and `}` brackets:
293
- *
294
- * ```ts
295
- * const { text, selection } = _parseView( 'f{ooba}r' );
296
- * ```
297
- *
298
- * Ranges placed outside text nodes should be marked using `[` and `]` brackets:
299
- *
300
- * ```ts
301
- * const { root, selection } = _parseView( '<p>[<b>foobar</b>]</p>' );
302
- * ```
303
- *
304
- * ** Note: **
305
- * It is possible to unify selection markers to `[` and `]` for both (inside and outside text)
306
- * by setting `sameSelectionCharacters=true` option. It is mainly used when the view _parseView option is used by model utilities.
307
- *
308
- * Sometimes there is a need for defining the order of ranges inside the created selection. This can be achieved by providing
309
- * the range order array as an additional parameter:
310
- *
311
- * ```ts
312
- * const { root, selection } = _parseView( '{fo}ob{ar}{ba}z', { order: [ 2, 3, 1 ] } );
313
- * ```
314
- *
315
- * In the example above, the first range (`{fo}`) will be added to the selection as the second one, the second range (`{ar}`) will be
316
- * added as the third and the third range (`{ba}`) will be added as the first one.
317
- *
318
- * If the selection's last range should be added as a backward one
319
- * (so the {@link module:engine/view/documentselection~ViewDocumentSelection#anchor selection anchor} is represented
320
- * by the `end` position and {@link module:engine/view/documentselection~ViewDocumentSelection#focus selection focus} is
321
- * represented by the `start` position), use the `lastRangeBackward` flag:
322
- *
323
- * ```ts
324
- * const { root, selection } = _parseView( `{foo}bar{baz}`, { lastRangeBackward: true } );
325
- * ```
326
- *
327
- * Some more examples and edge cases:
328
- *
329
- * ```ts
330
- * // Returns an empty document fragment.
331
- * _parseView( '' );
332
- *
333
- * // Returns an empty document fragment and a collapsed selection.
334
- * const { root, selection } = _parseView( '[]' );
335
- *
336
- * // Returns an element and a selection that is placed inside the document fragment containing that element.
337
- * const { root, selection } = _parseView( '[<a></a>]' );
338
- * ```
339
- *
340
- * @param data An HTML-like string to be parsed.
341
- * @param options.order An array with the order of parsed ranges added to the returned
342
- * {@link module:engine/view/documentselection~ViewDocumentSelection Selection} instance. Each element should represent the
343
- * desired position of each range in the selection instance. For example: `[2, 3, 1]` means that the first range will be
344
- * placed as the second, the second as the third and the third as the first.
345
- * @param options.lastRangeBackward If set to `true`, the last range will be added as backward to the returned
346
- * {@link module:engine/view/documentselection~ViewDocumentSelection selection} instance.
347
- * @param options.rootElement The default root to use when parsing elements.
348
- * When set to `null`, the root element will be created automatically. If set to
349
- * {@link module:engine/view/element~ViewElement Element} or
350
- * {@link module:engine/view/documentfragment~ViewDocumentFragment DocumentFragment},
351
- * this node will be used as the root for all parsed nodes.
352
- * @param options.sameSelectionCharacters When set to `false`, the selection inside the text should be marked using
353
- * `{` and `}` and the selection outside the ext using `[` and `]`. When set to `true`, both should be marked with `[` and `]` only.
354
- * @returns Returns the parsed view node or an object with two fields: `view` and `selection` when selection ranges were included in the
355
- * data to parse.
356
- */
357
- export function _parseView(data, options = {}) {
358
- const viewDocument = new ViewDocument(new StylesProcessor());
359
- options.order = options.order || [];
360
- const rangeParser = new RangeParser({
361
- sameSelectionCharacters: options.sameSelectionCharacters
362
- });
363
- const processor = new XmlDataProcessor(viewDocument, {
364
- namespaces: Object.keys(allowedTypes)
365
- });
366
- if (options.inlineObjectElements) {
367
- processor.domConverter.inlineObjectElements.push(...options.inlineObjectElements);
368
- }
369
- // Convert data to view.
370
- let view = processor.toView(data);
371
- // At this point we have a view tree with Elements that could have names like `attribute:b:1`. In the next step
372
- // we need to parse Element's names and convert them to ViewAttributeElements and ViewContainerElements.
373
- view = _convertViewElements(view);
374
- // If custom root is provided - move all nodes there.
375
- if (options.rootElement) {
376
- const root = options.rootElement;
377
- const nodes = view._removeChildren(0, view.childCount);
378
- root._removeChildren(0, root.childCount);
379
- root._appendChild(nodes);
380
- view = root;
381
- }
382
- // Parse ranges included in view text nodes.
383
- const ranges = rangeParser._parseView(view, options.order);
384
- // If only one element is returned inside DocumentFragment - return that element.
385
- if (view.is('documentFragment') && view.childCount === 1) {
386
- view = view.getChild(0);
387
- }
388
- // When ranges are present - return object containing view, and selection.
389
- if (ranges.length) {
390
- const selection = new ViewDocumentSelection(ranges, { backward: !!options.lastRangeBackward });
391
- return {
392
- view,
393
- selection
394
- };
395
- }
396
- // If single element is returned without selection - remove it from parent and return detached element.
397
- if (view.parent) {
398
- view._remove();
399
- }
400
- return view;
401
- }
402
- /**
403
- * Private helper class used for converting ranges represented as text inside view {@link module:engine/view/text~ViewText text nodes}.
404
- */
405
- class RangeParser {
406
- sameSelectionCharacters;
407
- _positions;
408
- /**
409
- * Creates a range parser instance.
410
- *
411
- * @param options The range parser configuration.
412
- * @param options.sameSelectionCharacters When set to `true`, the selection inside the text is marked as
413
- * `{` and `}` and the selection outside the text as `[` and `]`. When set to `false`, both are marked as `[` and `]`.
414
- */
415
- constructor(options) {
416
- this.sameSelectionCharacters = !!options.sameSelectionCharacters;
417
- }
418
- /**
419
- * Parses the view and returns ranges represented inside {@link module:engine/view/text~ViewText text nodes}.
420
- * The method will remove all occurrences of `{`, `}`, `[` and `]` from found text nodes. If a text node is empty after
421
- * the process, it will be removed, too.
422
- *
423
- * @param node The starting node.
424
- * @param order The order of ranges. Each element should represent the desired position of the range after
425
- * sorting. For example: `[2, 3, 1]` means that the first range will be placed as the second, the second as the third and the third
426
- * as the first.
427
- * @returns An array with ranges found.
428
- */
429
- _parseView(node, order) {
430
- this._positions = [];
431
- // Remove all range brackets from view nodes and save their positions.
432
- this._getPositions(node);
433
- // Create ranges using gathered positions.
434
- let ranges = this._createRanges();
435
- // Sort ranges if needed.
436
- if (order.length) {
437
- if (order.length != ranges.length) {
438
- throw new Error(`Parse error - there are ${ranges.length} ranges found, but ranges order array contains ${order.length} elements.`);
439
- }
440
- ranges = this._sortRanges(ranges, order);
441
- }
442
- return ranges;
443
- }
444
- /**
445
- * Gathers positions of brackets inside the view tree starting from the provided node. The method will remove all occurrences of
446
- * `{`, `}`, `[` and `]` from found text nodes. If a text node is empty after the process, it will be removed, too.
447
- *
448
- * @param node Staring node.
449
- */
450
- _getPositions(node) {
451
- if (node.is('documentFragment') || node.is('element')) {
452
- // Copy elements into the array, when nodes will be removed from parent node this array will still have all the
453
- // items needed for iteration.
454
- const children = [...node.getChildren()];
455
- for (const child of children) {
456
- this._getPositions(child);
457
- }
458
- }
459
- if (node.is('$text')) {
460
- const regexp = new RegExp(`[${TEXT_RANGE_START_TOKEN}${TEXT_RANGE_END_TOKEN}\\${ELEMENT_RANGE_END_TOKEN}\\${ELEMENT_RANGE_START_TOKEN}]`, 'g');
461
- let text = node.data;
462
- let match;
463
- let offset = 0;
464
- const brackets = [];
465
- // Remove brackets from text and store info about offset inside text node.
466
- while ((match = regexp.exec(text))) {
467
- const index = match.index;
468
- const bracket = match[0];
469
- brackets.push({
470
- bracket,
471
- textOffset: index - offset
472
- });
473
- offset++;
474
- }
475
- text = text.replace(regexp, '');
476
- node._data = text;
477
- const index = node.index;
478
- const parent = node.parent;
479
- // Remove empty text nodes.
480
- if (!text) {
481
- node._remove();
482
- }
483
- for (const item of brackets) {
484
- // Non-empty text node.
485
- if (text) {
486
- if (this.sameSelectionCharacters ||
487
- (!this.sameSelectionCharacters &&
488
- (item.bracket == TEXT_RANGE_START_TOKEN || item.bracket == TEXT_RANGE_END_TOKEN))) {
489
- // Store information about text range delimiter.
490
- this._positions.push({
491
- bracket: item.bracket,
492
- position: new ViewPosition(node, item.textOffset)
493
- });
494
- }
495
- else {
496
- // Check if element range delimiter is not placed inside text node.
497
- if (!this.sameSelectionCharacters && item.textOffset !== 0 && item.textOffset !== text.length) {
498
- throw new Error(`Parse error - range delimiter '${item.bracket}' is placed inside text node.`);
499
- }
500
- // If bracket is placed at the end of the text node - it should be positioned after it.
501
- const offset = (item.textOffset === 0 ? index : index + 1);
502
- // Store information about element range delimiter.
503
- this._positions.push({
504
- bracket: item.bracket,
505
- position: new ViewPosition(parent, offset)
506
- });
507
- }
508
- }
509
- else {
510
- if (!this.sameSelectionCharacters &&
511
- item.bracket == TEXT_RANGE_START_TOKEN ||
512
- item.bracket == TEXT_RANGE_END_TOKEN) {
513
- throw new Error(`Parse error - text range delimiter '${item.bracket}' is placed inside empty text node. `);
514
- }
515
- // Store information about element range delimiter.
516
- this._positions.push({
517
- bracket: item.bracket,
518
- position: new ViewPosition(parent, index)
519
- });
520
- }
521
- }
522
- }
523
- }
524
- /**
525
- * Sorts ranges in a given order. Range order should be an array and each element should represent the desired position
526
- * of the range after sorting.
527
- * For example: `[2, 3, 1]` means that the first range will be placed as the second, the second as the third and the third
528
- * as the first.
529
- *
530
- * @param ranges Ranges to sort.
531
- * @param rangesOrder An array with new range order.
532
- * @returns Sorted ranges array.
533
- */
534
- _sortRanges(ranges, rangesOrder) {
535
- const sortedRanges = [];
536
- let index = 0;
537
- for (const newPosition of rangesOrder) {
538
- if (ranges[newPosition - 1] === undefined) {
539
- throw new Error('Parse error - provided ranges order is invalid.');
540
- }
541
- sortedRanges[newPosition - 1] = ranges[index];
542
- index++;
543
- }
544
- return sortedRanges;
545
- }
546
- /**
547
- * Uses all found bracket positions to create ranges from them.
548
- */
549
- _createRanges() {
550
- const ranges = [];
551
- let range = null;
552
- for (const item of this._positions) {
553
- // When end of range is found without opening.
554
- if (!range && (item.bracket == ELEMENT_RANGE_END_TOKEN || item.bracket == TEXT_RANGE_END_TOKEN)) {
555
- throw new Error(`Parse error - end of range was found '${item.bracket}' but range was not started before.`);
556
- }
557
- // When second start of range is found when one is already opened - selection does not allow intersecting
558
- // ranges.
559
- if (range && (item.bracket == ELEMENT_RANGE_START_TOKEN || item.bracket == TEXT_RANGE_START_TOKEN)) {
560
- throw new Error(`Parse error - start of range was found '${item.bracket}' but one range is already started.`);
561
- }
562
- if (item.bracket == ELEMENT_RANGE_START_TOKEN || item.bracket == TEXT_RANGE_START_TOKEN) {
563
- range = new ViewRange(item.position, item.position);
564
- }
565
- else {
566
- range.end = item.position;
567
- ranges.push(range);
568
- range = null;
569
- }
570
- }
571
- // Check if all ranges have proper ending.
572
- if (range !== null) {
573
- throw new Error('Parse error - range was started but no end delimiter was found.');
574
- }
575
- return ranges;
576
- }
577
- }
578
- /**
579
- * Private helper class used for converting the view tree to a string.
580
- */
581
- class ViewStringify {
582
- root;
583
- selection;
584
- ranges;
585
- showType;
586
- showPriority;
587
- showAttributeElementId;
588
- ignoreRoot;
589
- sameSelectionCharacters;
590
- renderUIElements;
591
- renderRawElements;
592
- domConverter;
593
- skipListItemIds;
594
- /**
595
- * Creates a view stringify instance.
596
- *
597
- * @param selection A selection whose ranges should also be converted to a string.
598
- * @param options An options object.
599
- * @param options.showType When set to `true`, the type of elements will be printed (`<container:p>`
600
- * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
601
- * @param options.showPriority When set to `true`, the attribute element's priority will be printed.
602
- * @param options.ignoreRoot When set to `true`, the root's element opening and closing tag will not
603
- * be outputted.
604
- * @param options.sameSelectionCharacters When set to `true`, the selection inside the text is marked as
605
- * `{` and `}` and the selection outside the text as `[` and `]`. When set to `false`, both are marked as `[` and `]`.
606
- * @param options.renderUIElements When set to `true`, the inner content of each
607
- * {@link module:engine/view/uielement~ViewUIElement} will be printed.
608
- * @param options.renderRawElements When set to `true`, the inner content of each
609
- * @param options.domConverter When set to an actual {@link module:engine/view/domconverter~ViewDomConverter ViewDomConverter}
610
- * instance, it lets the conversion go through exactly the same flow the editing view is going through,
611
- * i.e. with view data filtering. Otherwise the simple stub is used.
612
- * {@link module:engine/view/rawelement~ViewRawElement} will be printed.
613
- * @param options.skipListItemIds When set to `true`, `<li>` elements will not have `listItemId` attribute. By default it's hidden
614
- * because it's randomly generated and hard to verify properly, while bringing little value.
615
- */
616
- constructor(root, selection, options) {
617
- this.root = root;
618
- this.selection = selection;
619
- this.ranges = [];
620
- if (selection) {
621
- this.ranges = [...selection.getRanges()];
622
- }
623
- this.showType = !!options.showType;
624
- this.showPriority = !!options.showPriority;
625
- this.showAttributeElementId = !!options.showAttributeElementId;
626
- this.ignoreRoot = !!options.ignoreRoot;
627
- this.sameSelectionCharacters = !!options.sameSelectionCharacters;
628
- this.renderUIElements = !!options.renderUIElements;
629
- this.renderRawElements = !!options.renderRawElements;
630
- this.domConverter = options.domConverter || domConverterStub;
631
- this.skipListItemIds = options.skipListItemIds !== undefined ? !!options.skipListItemIds : true;
632
- }
633
- /**
634
- * Converts the view to a string.
635
- *
636
- * @returns String representation of the view elements.
637
- */
638
- stringify() {
639
- let result = '';
640
- this._walkView(this.root, chunk => {
641
- result += chunk;
642
- });
643
- if (this.skipListItemIds) {
644
- result = result.replaceAll(/ data-list-item-id="[^"]+"/g, '');
645
- }
646
- return result;
647
- }
648
- /**
649
- * Executes a simple walker that iterates over all elements in the view tree starting from the root element.
650
- * Calls the `callback` with parsed chunks of string data.
651
- */
652
- _walkView(root, callback) {
653
- const ignore = this.ignoreRoot && this.root === root;
654
- if (root.is('element') || root.is('documentFragment')) {
655
- if (root.is('element') && !ignore) {
656
- callback(this._stringifyElementOpen(root));
657
- }
658
- if ((this.renderUIElements && root.is('uiElement'))) {
659
- callback(root.render(document, this.domConverter).innerHTML);
660
- }
661
- else if (this.renderRawElements && root.is('rawElement')) {
662
- // There's no DOM element for "root" to pass to render(). Creating
663
- // a surrogate container to render the children instead.
664
- const rawContentContainer = document.createElement('div');
665
- root.render(rawContentContainer, this.domConverter);
666
- callback(rawContentContainer.innerHTML);
667
- }
668
- else {
669
- let offset = 0;
670
- callback(this._stringifyElementRanges(root, offset));
671
- for (const child of root.getChildren()) {
672
- this._walkView(child, callback);
673
- offset++;
674
- callback(this._stringifyElementRanges(root, offset));
675
- }
676
- }
677
- if (root.is('element') && !ignore) {
678
- callback(this._stringifyElementClose(root));
679
- }
680
- }
681
- if (root.is('$text')) {
682
- callback(this._stringifyTextRanges(root));
683
- }
684
- }
685
- /**
686
- * Checks if a given {@link module:engine/view/element~ViewElement element} has
687
- * a {@link module:engine/view/range~ViewRange#start range start}
688
- * or a {@link module:engine/view/range~ViewRange#start range end} placed at a given offset and returns its string representation.
689
- */
690
- _stringifyElementRanges(element, offset) {
691
- let start = '';
692
- let end = '';
693
- let collapsed = '';
694
- for (const range of this.ranges) {
695
- if (range.start.parent == element && range.start.offset === offset) {
696
- if (range.isCollapsed) {
697
- collapsed += ELEMENT_RANGE_START_TOKEN + ELEMENT_RANGE_END_TOKEN;
698
- }
699
- else {
700
- start += ELEMENT_RANGE_START_TOKEN;
701
- }
702
- }
703
- if (range.end.parent === element && range.end.offset === offset && !range.isCollapsed) {
704
- end += ELEMENT_RANGE_END_TOKEN;
705
- }
706
- }
707
- return end + collapsed + start;
708
- }
709
- /**
710
- * Checks if a given {@link module:engine/view/element~ViewElement Text node} has a
711
- * {@link module:engine/view/range~ViewRange#start range start} or a
712
- * {@link module:engine/view/range~ViewRange#start range end} placed somewhere inside. Returns a string representation of text
713
- * with range delimiters placed inside.
714
- */
715
- _stringifyTextRanges(node) {
716
- const length = node.data.length;
717
- const data = node.data.split('');
718
- let rangeStartToken, rangeEndToken;
719
- if (this.sameSelectionCharacters) {
720
- rangeStartToken = ELEMENT_RANGE_START_TOKEN;
721
- rangeEndToken = ELEMENT_RANGE_END_TOKEN;
722
- }
723
- else {
724
- rangeStartToken = TEXT_RANGE_START_TOKEN;
725
- rangeEndToken = TEXT_RANGE_END_TOKEN;
726
- }
727
- // Add one more element for ranges ending after last character in text.
728
- data[length] = '';
729
- // Represent each letter as object with information about opening/closing ranges at each offset.
730
- const result = data.map(letter => {
731
- return {
732
- letter,
733
- start: '',
734
- end: '',
735
- collapsed: ''
736
- };
737
- });
738
- for (const range of this.ranges) {
739
- const start = range.start;
740
- const end = range.end;
741
- if (start.parent == node && start.offset >= 0 && start.offset <= length) {
742
- if (range.isCollapsed) {
743
- result[end.offset].collapsed += rangeStartToken + rangeEndToken;
744
- }
745
- else {
746
- result[start.offset].start += rangeStartToken;
747
- }
748
- }
749
- if (end.parent == node && end.offset >= 0 && end.offset <= length && !range.isCollapsed) {
750
- result[end.offset].end += rangeEndToken;
751
- }
752
- }
753
- return result.map(item => item.end + item.collapsed + item.start + item.letter).join('');
754
- }
755
- /**
756
- * Converts the passed {@link module:engine/view/element~ViewElement element} to an opening tag.
757
- *
758
- * Depending on the current configuration, the opening tag can be simple (`<a>`), contain a type prefix (`<container:p>`,
759
- * `<attribute:a>` or `<empty:img>`), contain priority information ( `<attribute:a view-priority="20">` ),
760
- * or contain element id ( `<attribute:span view-id="foo">` ). Element attributes will also be included
761
- * (`<a href="https://ckeditor.com" name="foobar">`).
762
- */
763
- _stringifyElementOpen(element) {
764
- const priority = this._stringifyElementPriority(element);
765
- const id = this._stringifyElementId(element);
766
- const type = this._stringifyElementType(element);
767
- const name = [type, element.name].filter(i => i !== '').join(':');
768
- const attributes = this._stringifyElementAttributes(element);
769
- const parts = [name, priority, id, attributes];
770
- return `<${parts.filter(i => i !== '').join(' ')}>`;
771
- }
772
- /**
773
- * Converts the passed {@link module:engine/view/element~ViewElement element} to a closing tag.
774
- * Depending on the current configuration, the closing tag can be simple (`</a>`) or contain a type prefix (`</container:p>`,
775
- * `</attribute:a>` or `</empty:img>`).
776
- */
777
- _stringifyElementClose(element) {
778
- const type = this._stringifyElementType(element);
779
- const name = [type, element.name].filter(i => i !== '').join(':');
780
- return `</${name}>`;
781
- }
782
- /**
783
- * Converts the passed {@link module:engine/view/element~ViewElement element's} type to its string representation
784
- *
785
- * Returns:
786
- * * 'attribute' for {@link module:engine/view/attributeelement~ViewAttributeElement attribute elements},
787
- * * 'container' for {@link module:engine/view/containerelement~ViewContainerElement container elements},
788
- * * 'empty' for {@link module:engine/view/emptyelement~ViewEmptyElement empty elements},
789
- * * 'ui' for {@link module:engine/view/uielement~ViewUIElement UI elements},
790
- * * 'raw' for {@link module:engine/view/rawelement~ViewRawElement raw elements},
791
- * * an empty string when the current configuration is preventing showing elements' types.
792
- */
793
- _stringifyElementType(element) {
794
- if (this.showType) {
795
- for (const type in allowedTypes) {
796
- if (element instanceof allowedTypes[type]) {
797
- return type;
798
- }
799
- }
800
- }
801
- return '';
802
- }
803
- /**
804
- * Converts the passed {@link module:engine/view/element~ViewElement element} to its priority representation.
805
- *
806
- * The priority string representation will be returned when the passed element is an instance of
807
- * {@link module:engine/view/attributeelement~ViewAttributeElement attribute element} and the current configuration allows to show the
808
- * priority. Otherwise returns an empty string.
809
- */
810
- _stringifyElementPriority(element) {
811
- if (this.showPriority && element.is('attributeElement')) {
812
- return `view-priority="${element.priority}"`;
813
- }
814
- return '';
815
- }
816
- /**
817
- * Converts the passed {@link module:engine/view/element~ViewElement element} to its id representation.
818
- *
819
- * The id string representation will be returned when the passed element is an instance of
820
- * {@link module:engine/view/attributeelement~ViewAttributeElement attribute element}, the element has an id
821
- * and the current configuration allows to show the id. Otherwise returns an empty string.
822
- */
823
- _stringifyElementId(element) {
824
- if (this.showAttributeElementId && element.is('attributeElement') && element.id) {
825
- return `view-id="${element.id}"`;
826
- }
827
- return '';
828
- }
829
- /**
830
- * Converts the passed {@link module:engine/view/element~ViewElement element} attributes to their string representation.
831
- * If an element has no attributes, an empty string is returned.
832
- */
833
- _stringifyElementAttributes(element) {
834
- const attributes = [];
835
- const keys = [...element.getAttributeKeys()].sort();
836
- for (const attribute of keys) {
837
- let attributeValue;
838
- if (attribute === 'class') {
839
- attributeValue = [...element.getClassNames()]
840
- .sort()
841
- .join(' ');
842
- }
843
- else if (attribute === 'style') {
844
- attributeValue = [...element.getStyleNames()]
845
- .sort()
846
- .map(style => `${style}:${element.getStyle(style).replace(/"/g, '&quot;')}`)
847
- .join(';');
848
- }
849
- else {
850
- attributeValue = element.getAttribute(attribute);
851
- }
852
- attributes.push(`${attribute}="${attributeValue}"`);
853
- }
854
- return attributes.join(' ');
855
- }
856
- }
857
- /**
858
- * Converts {@link module:engine/view/element~ViewElement elements} to
859
- * {@link module:engine/view/attributeelement~ViewAttributeElement attribute elements},
860
- * {@link module:engine/view/containerelement~ViewContainerElement container elements},
861
- * {@link module:engine/view/emptyelement~ViewEmptyElement empty elements} or
862
- * {@link module:engine/view/uielement~ViewUIElement UI elements}.
863
- * It converts the whole tree starting from the `rootNode`. The conversion is based on element names.
864
- * See the `_convertElement` method for more details.
865
- *
866
- * @param rootNode The root node to convert.
867
- * @returns The root node of converted elements.
868
- */
869
- function _convertViewElements(rootNode) {
870
- if (rootNode.is('element') || rootNode.is('documentFragment')) {
871
- // Convert element or leave document fragment.
872
- const convertedElement = rootNode.is('documentFragment') ?
873
- new ViewDocumentFragment(rootNode.document) :
874
- _convertElement(rootNode.document, rootNode);
875
- // Convert all child nodes.
876
- // Cache the nodes in array. Otherwise, we would skip some nodes because during iteration we move nodes
877
- // from `rootNode` to `convertedElement`. This would interfere with iteration.
878
- for (const child of [...rootNode.getChildren()]) {
879
- if (convertedElement.is('emptyElement')) {
880
- throw new Error('Parse error - cannot parse inside ViewEmptyElement.');
881
- }
882
- else if (convertedElement.is('uiElement')) {
883
- throw new Error('Parse error - cannot parse inside UIElement.');
884
- }
885
- else if (convertedElement.is('rawElement')) {
886
- throw new Error('Parse error - cannot parse inside RawElement.');
887
- }
888
- convertedElement._appendChild(_convertViewElements(child));
889
- }
890
- return convertedElement;
891
- }
892
- return rootNode;
893
- }
894
- /**
895
- * Converts an {@link module:engine/view/element~ViewElement element} to
896
- * {@link module:engine/view/attributeelement~ViewAttributeElement attribute element},
897
- * {@link module:engine/view/containerelement~ViewContainerElement container element},
898
- * {@link module:engine/view/emptyelement~ViewEmptyElement empty element} or
899
- * {@link module:engine/view/uielement~ViewUIElement UI element}.
900
- * If the element's name is in the format of `attribute:b`, it will be converted to
901
- * an {@link module:engine/view/attributeelement~ViewAttributeElement attribute element} with a priority of 11.
902
- * Additionally, attribute elements may have specified priority (for example `view-priority="11"`) and/or
903
- * id (for example `view-id="foo"`).
904
- * If the element's name is in the format of `container:p`, it will be converted to
905
- * a {@link module:engine/view/containerelement~ViewContainerElement container element}.
906
- * If the element's name is in the format of `empty:img`, it will be converted to
907
- * an {@link module:engine/view/emptyelement~ViewEmptyElement empty element}.
908
- * If the element's name is in the format of `ui:span`, it will be converted to
909
- * a {@link module:engine/view/uielement~ViewUIElement UI element}.
910
- * If the element's name does not contain any additional information, a {@link module:engine/view/element~ViewElement view Element} will be
911
- * returned.
912
- *
913
- * @param viewElement A view element to convert.
914
- * @returns A tree view element converted according to its name.
915
- */
916
- function _convertElement(viewDocument, viewElement) {
917
- const info = _convertElementNameAndInfo(viewElement);
918
- const ElementConstructor = allowedTypes[info.type];
919
- const newElement = ElementConstructor ? new ElementConstructor(viewDocument, info.name) : new ViewElement(viewDocument, info.name);
920
- if (newElement.is('attributeElement')) {
921
- if (info.priority !== null) {
922
- newElement._priority = info.priority;
923
- }
924
- if (info.id !== null) {
925
- newElement._id = info.id;
926
- }
927
- }
928
- // Move attributes.
929
- for (const attributeKey of viewElement.getAttributeKeys()) {
930
- newElement._setAttribute(attributeKey, viewElement.getAttribute(attributeKey));
931
- }
932
- return newElement;
933
- }
934
- /**
935
- * Converts the `view-priority` attribute and the {@link module:engine/view/element~ViewElement#name element's name} information needed for
936
- * creating {@link module:engine/view/attributeelement~ViewAttributeElement attribute element},
937
- * {@link module:engine/view/containerelement~ViewContainerElement container element},
938
- * {@link module:engine/view/emptyelement~ViewEmptyElement empty element} or
939
- * {@link module:engine/view/uielement~ViewUIElement UI element}.
940
- * The name can be provided in two formats: as a simple element's name (`div`), or as a type and name (`container:div`,
941
- * `attribute:span`, `empty:img`, `ui:span`);
942
- *
943
- * @param viewElement The element whose name should be converted.
944
- * @returns An object with parsed information:
945
- * * `name` The parsed name of the element.
946
- * * `type` The parsed type of the element. It can be `attribute`, `container` or `empty`.
947
- * * `priority` The parsed priority of the element.
948
- */
949
- function _convertElementNameAndInfo(viewElement) {
950
- const parts = viewElement.name.split(':');
951
- const priority = _convertPriority(viewElement.getAttribute('view-priority'));
952
- const id = viewElement.hasAttribute('view-id') ? viewElement.getAttribute('view-id') : null;
953
- viewElement._removeAttribute('view-priority');
954
- viewElement._removeAttribute('view-id');
955
- if (parts.length == 1) {
956
- return {
957
- name: parts[0],
958
- type: priority !== null ? 'attribute' : null,
959
- priority,
960
- id
961
- };
962
- }
963
- // Check if type and name: container:div.
964
- const type = _convertType(parts[0]);
965
- if (type) {
966
- return {
967
- name: parts[1],
968
- type,
969
- priority,
970
- id
971
- };
972
- }
973
- throw new Error(`Parse error - cannot parse element's name: ${viewElement.name}.`);
974
- }
975
- /**
976
- * Checks if the element's type is allowed. Returns `attribute`, `container`, `empty` or `null`.
977
- */
978
- function _convertType(type) {
979
- return type in allowedTypes ? type : null;
980
- }
981
- /**
982
- * Checks if a given priority is allowed. Returns null if the priority cannot be converted.
983
- */
984
- function _convertPriority(priorityString) {
985
- const priority = parseInt(priorityString, 10);
986
- if (!isNaN(priority)) {
987
- return priority;
988
- }
989
- return null;
990
- }