@ckeditor/ckeditor5-engine 41.4.0-alpha.0 → 41.4.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.
package/dist/index.js CHANGED
@@ -8059,7 +8059,11 @@ class DomConverter {
8059
8059
  }
8060
8060
  if (options.withChildren !== false) {
8061
8061
  for (const child of this.viewChildrenToDom(viewElementOrFragment, options)){
8062
- domElement.appendChild(child);
8062
+ if (domElement instanceof HTMLTemplateElement) {
8063
+ domElement.content.appendChild(child);
8064
+ } else {
8065
+ domElement.appendChild(child);
8066
+ }
8063
8067
  }
8064
8068
  }
8065
8069
  return domElement;
@@ -8278,8 +8282,19 @@ class DomConverter {
8278
8282
  * @param inlineNodes An array that will be populated with inline nodes. It's used internally for whitespace processing.
8279
8283
  * @returns View nodes.
8280
8284
  */ *domChildrenToView(domElement, options = {}, inlineNodes = []) {
8281
- for(let i = 0; i < domElement.childNodes.length; i++){
8282
- const domChild = domElement.childNodes[i];
8285
+ // Get child nodes from content document fragment if element is template
8286
+ let childNodes = [];
8287
+ if (domElement instanceof HTMLTemplateElement) {
8288
+ childNodes = [
8289
+ ...domElement.content.childNodes
8290
+ ];
8291
+ } else {
8292
+ childNodes = [
8293
+ ...domElement.childNodes
8294
+ ];
8295
+ }
8296
+ for(let i = 0; i < childNodes.length; i++){
8297
+ const domChild = childNodes[i];
8283
8298
  const generator = this._domToView(domChild, options, inlineNodes);
8284
8299
  // Get the first yielded value or a returned value.
8285
8300
  const viewChild = generator.next().value;
@@ -19132,7 +19147,7 @@ class UpcastHelpers extends ConversionHelpers {
19132
19147
  * @returns Universal converter for view {@link module:engine/view/documentfragment~DocumentFragment fragments} and
19133
19148
  * {@link module:engine/view/element~Element elements} that returns
19134
19149
  * {@link module:engine/model/documentfragment~DocumentFragment model fragment} with children of converted view item.
19135
- */ function convertToModelFragment() {
19150
+ */ function convertToModelFragment$1() {
19136
19151
  return (evt, data, conversionApi)=>{
19137
19152
  // Second argument in `consumable.consume` is discarded for ViewDocumentFragment but is needed for ViewElement.
19138
19153
  if (!data.modelRange && conversionApi.consumable.consume(data.viewItem, {
@@ -22591,10 +22606,10 @@ class DataController extends EmitterMixin() {
22591
22606
  this.upcastDispatcher.on('text', convertText(), {
22592
22607
  priority: 'lowest'
22593
22608
  });
22594
- this.upcastDispatcher.on('element', convertToModelFragment(), {
22609
+ this.upcastDispatcher.on('element', convertToModelFragment$1(), {
22595
22610
  priority: 'lowest'
22596
22611
  });
22597
- this.upcastDispatcher.on('documentFragment', convertToModelFragment(), {
22612
+ this.upcastDispatcher.on('documentFragment', convertToModelFragment$1(), {
22598
22613
  priority: 'lowest'
22599
22614
  });
22600
22615
  ObservableMixin().prototype.decorate.call(this, 'init');
@@ -33969,5 +33984,1387 @@ function getBorderPositionReducer(which) {
33969
33984
  ]);
33970
33985
  }
33971
33986
 
33972
- export { AttributeElement, AttributeOperation, BubblingEventInfo, ClickObserver, Conversion, DataController, DataTransfer, DocumentFragment, DocumentSelection, DomConverter, DomEventData, DomEventObserver, DowncastWriter, EditingController, View as EditingView, Element, FocusObserver, History, HtmlDataProcessor, InsertOperation, LivePosition, LiveRange, MarkerOperation, Matcher, MergeOperation, Model, MouseObserver, MoveOperation, NoOperation, Observer, OperationFactory, Position, Range, RenameOperation, Renderer, RootAttributeOperation, RootOperation, SplitOperation, StylesMap, StylesProcessor, TabObserver, Text, TextProxy, TreeWalker, UpcastWriter, AttributeElement as ViewAttributeElement, ContainerElement as ViewContainerElement, Document$1 as ViewDocument, DocumentFragment$1 as ViewDocumentFragment, EditableElement as ViewEditableElement, Element$1 as ViewElement, EmptyElement as ViewEmptyElement, RawElement as ViewRawElement, RootEditableElement as ViewRootEditableElement, Text$1 as ViewText, TreeWalker$1 as ViewTreeWalker, UIElement as ViewUIElement, addBackgroundRules, addBorderRules, addMarginRules, addPaddingRules, disablePlaceholder, enablePlaceholder, getBoxSidesShorthandValue, getBoxSidesValueReducer, getBoxSidesValues, getFillerOffset$4 as getFillerOffset, getPositionShorthandNormalizer, getShorthandValues, hidePlaceholder, isAttachment, isColor, isLength, isLineStyle, isPercentage, isPosition, isRepeat, isURL, needsPlaceholder, showPlaceholder, transformSets };
33987
+ class XmlDataProcessor {
33988
+ /**
33989
+ * Converts the provided {@link module:engine/view/documentfragment~DocumentFragment document fragment}
33990
+ * to data format &ndash; in this case an XML string.
33991
+ *
33992
+ * @returns An XML string.
33993
+ */ toData(viewFragment) {
33994
+ // Convert view DocumentFragment to DOM DocumentFragment.
33995
+ const domFragment = this.domConverter.viewToDom(viewFragment);
33996
+ // Convert DOM DocumentFragment to XML output.
33997
+ // There is no need to use dedicated for XML serializing method because BasicHtmlWriter works well in this case.
33998
+ return this.htmlWriter.getHtml(domFragment);
33999
+ }
34000
+ /**
34001
+ * Converts the provided XML string to a view tree.
34002
+ *
34003
+ * @param data An XML string.
34004
+ * @returns A converted view element.
34005
+ */ toView(data) {
34006
+ // Convert input XML data to DOM DocumentFragment.
34007
+ const domFragment = this._toDom(data);
34008
+ // Convert DOM DocumentFragment to view DocumentFragment.
34009
+ return this.domConverter.domToView(domFragment, {
34010
+ keepOriginalCase: true,
34011
+ skipComments: this.skipComments
34012
+ });
34013
+ }
34014
+ /**
34015
+ * Registers a {@link module:engine/view/matcher~MatcherPattern} for view elements whose content should be treated as raw data
34016
+ * and not processed during the conversion from XML to view elements.
34017
+ *
34018
+ * The raw data can be later accessed by a
34019
+ * {@link module:engine/view/element~Element#getCustomProperty custom property of a view element} called `"$rawContent"`.
34020
+ *
34021
+ * @param pattern Pattern matching all view elements whose content should be treated as raw data.
34022
+ */ registerRawContentMatcher(pattern) {
34023
+ this.domConverter.registerRawContentMatcher(pattern);
34024
+ }
34025
+ /**
34026
+ * If the processor is set to use marked fillers, it will insert `&nbsp;` fillers wrapped in `<span>` elements
34027
+ * (`<span data-cke-filler="true">&nbsp;</span>`) instead of regular `&nbsp;` characters.
34028
+ *
34029
+ * This mode allows for a more precise handling of block fillers (so they do not leak into editor content) but
34030
+ * bloats the editor data with additional markup.
34031
+ *
34032
+ * This mode may be required by some features and will be turned on by them automatically.
34033
+ *
34034
+ * @param type Whether to use the default or the marked `&nbsp;` block fillers.
34035
+ */ useFillerType(type) {
34036
+ this.domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
34037
+ }
34038
+ /**
34039
+ * Converts an XML string to its DOM representation. Returns a document fragment containing nodes parsed from
34040
+ * the provided data.
34041
+ */ _toDom(data) {
34042
+ // Stringify namespaces.
34043
+ const namespaces = this.namespaces.map((nsp)=>`xmlns:${nsp}="nsp"`).join(' ');
34044
+ // Wrap data into root element with optional namespace definitions.
34045
+ data = `<xml ${namespaces}>${data}</xml>`;
34046
+ const parsedDocument = this.domParser.parseFromString(data, 'text/xml');
34047
+ // Parse validation.
34048
+ const parserError = parsedDocument.querySelector('parsererror');
34049
+ if (parserError) {
34050
+ throw new Error('Parse error - ' + parserError.textContent);
34051
+ }
34052
+ const fragment = parsedDocument.createDocumentFragment();
34053
+ const nodes = parsedDocument.documentElement.childNodes;
34054
+ while(nodes.length > 0){
34055
+ fragment.appendChild(nodes[0]);
34056
+ }
34057
+ return fragment;
34058
+ }
34059
+ /**
34060
+ * Creates a new instance of the XML data processor class.
34061
+ *
34062
+ * @param document The view document instance.
34063
+ * @param options Configuration options.
34064
+ * @param options.namespaces A list of namespaces allowed to use in the XML input.
34065
+ */ constructor(document, options = {}){
34066
+ this.skipComments = true;
34067
+ this.namespaces = options.namespaces || [];
34068
+ this.domParser = new DOMParser();
34069
+ this.domConverter = new DomConverter(document, {
34070
+ renderingMode: 'data'
34071
+ });
34072
+ this.htmlWriter = new BasicHtmlWriter();
34073
+ }
34074
+ }
34075
+
34076
+ const ELEMENT_RANGE_START_TOKEN = '[';
34077
+ const ELEMENT_RANGE_END_TOKEN = ']';
34078
+ const TEXT_RANGE_START_TOKEN = '{';
34079
+ const TEXT_RANGE_END_TOKEN = '}';
34080
+ const allowedTypes = {
34081
+ 'container': ContainerElement,
34082
+ 'attribute': AttributeElement,
34083
+ 'empty': EmptyElement,
34084
+ 'ui': UIElement,
34085
+ 'raw': RawElement
34086
+ };
34087
+ // Returns simplified implementation of {@link module:engine/view/domconverter~DomConverter#setContentOf DomConverter.setContentOf} method.
34088
+ // Used to render UIElement and RawElement.
34089
+ const domConverterStub = {
34090
+ setContentOf: (node, html)=>{
34091
+ node.innerHTML = html;
34092
+ }
34093
+ };
34094
+ /**
34095
+ * Writes the content of the {@link module:engine/view/document~Document document} to an HTML-like string.
34096
+ *
34097
+ * @param options.withoutSelection Whether to write the selection. When set to `true`, the selection will
34098
+ * not be included in the returned string.
34099
+ * @param options.rootName The name of the root from which the data should be stringified. If not provided,
34100
+ * the default `main` name will be used.
34101
+ * @param options.showType When set to `true`, the type of elements will be printed (`<container:p>`
34102
+ * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
34103
+ * @param options.showPriority When set to `true`, the attribute element's priority will be printed
34104
+ * (`<span view-priority="12">`, `<b view-priority="10">`).
34105
+ * @param options.showAttributeElementId When set to `true`, the attribute element's ID will be printed
34106
+ * (`<span id="marker:foo">`).
34107
+ * @param options.renderUIElements When set to `true`, the inner content of each
34108
+ * {@link module:engine/view/uielement~UIElement} will be printed.
34109
+ * @param options.renderRawElements When set to `true`, the inner content of each
34110
+ * {@link module:engine/view/rawelement~RawElement} will be printed.
34111
+ * @param options.domConverter When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
34112
+ * instance, it lets the conversion go through exactly the same flow the editing view is going through,
34113
+ * i.e. with view data filtering. Otherwise the simple stub is used.
34114
+ * @returns The stringified data.
34115
+ */ function getData$1(view, options = {}) {
34116
+ if (!(view instanceof View)) {
34117
+ throw new TypeError('View needs to be an instance of module:engine/view/view~View.');
34118
+ }
34119
+ const document1 = view.document;
34120
+ const withoutSelection = !!options.withoutSelection;
34121
+ const rootName = options.rootName || 'main';
34122
+ const root = document1.getRoot(rootName);
34123
+ const stringifyOptions = {
34124
+ showType: options.showType,
34125
+ showPriority: options.showPriority,
34126
+ renderUIElements: options.renderUIElements,
34127
+ renderRawElements: options.renderRawElements,
34128
+ ignoreRoot: true,
34129
+ domConverter: options.domConverter
34130
+ };
34131
+ return withoutSelection ? getData$1._stringify(root, null, stringifyOptions) : getData$1._stringify(root, document1.selection, stringifyOptions);
34132
+ }
34133
+ // Set stringify as getData private method - needed for testing/spying.
34134
+ getData$1._stringify = stringify$1;
34135
+ /**
34136
+ * Sets the content of a view {@link module:engine/view/document~Document document} provided as an HTML-like string.
34137
+ *
34138
+ * @param data An HTML-like string to write into the document.
34139
+ * @param options.rootName The root name where parsed data will be stored. If not provided,
34140
+ * the default `main` name will be used.
34141
+ */ function setData$1(view, data, options = {}) {
34142
+ if (!(view instanceof View)) {
34143
+ throw new TypeError('View needs to be an instance of module:engine/view/view~View.');
34144
+ }
34145
+ const document1 = view.document;
34146
+ const rootName = options.rootName || 'main';
34147
+ const root = document1.getRoot(rootName);
34148
+ view.change((writer)=>{
34149
+ const result = setData$1._parse(data, {
34150
+ rootElement: root
34151
+ });
34152
+ if (result.view && result.selection) {
34153
+ writer.setSelection(result.selection);
34154
+ }
34155
+ });
34156
+ }
34157
+ // Set parse as setData private method - needed for testing/spying.
34158
+ setData$1._parse = parse$1;
34159
+ /**
34160
+ * Converts view elements to HTML-like string representation.
34161
+ *
34162
+ * A root element can be provided as {@link module:engine/view/text~Text text}:
34163
+ *
34164
+ * ```ts
34165
+ * const text = downcastWriter.createText( 'foobar' );
34166
+ * stringify( text ); // 'foobar'
34167
+ * ```
34168
+ *
34169
+ * or as an {@link module:engine/view/element~Element element}:
34170
+ *
34171
+ * ```ts
34172
+ * const element = downcastWriter.createElement( 'p', null, downcastWriter.createText( 'foobar' ) );
34173
+ * stringify( element ); // '<p>foobar</p>'
34174
+ * ```
34175
+ *
34176
+ * or as a {@link module:engine/view/documentfragment~DocumentFragment document fragment}:
34177
+ *
34178
+ * ```ts
34179
+ * const text = downcastWriter.createText( 'foobar' );
34180
+ * const b = downcastWriter.createElement( 'b', { name: 'test' }, text );
34181
+ * const p = downcastWriter.createElement( 'p', { style: 'color:red;' } );
34182
+ * const fragment = downcastWriter.createDocumentFragment( [ p, b ] );
34183
+ *
34184
+ * stringify( fragment ); // '<p style="color:red;"></p><b name="test">foobar</b>'
34185
+ * ```
34186
+ *
34187
+ * Additionally, a {@link module:engine/view/documentselection~DocumentSelection selection} instance can be provided.
34188
+ * Ranges from the selection will then be included in the output data.
34189
+ * If a range position is placed inside the element node, it will be represented with `[` and `]`:
34190
+ *
34191
+ * ```ts
34192
+ * const text = downcastWriter.createText( 'foobar' );
34193
+ * const b = downcastWriter.createElement( 'b', null, text );
34194
+ * const p = downcastWriter.createElement( 'p', null, b );
34195
+ * const selection = downcastWriter.createSelection(
34196
+ * downcastWriter.createRangeIn( p )
34197
+ * );
34198
+ *
34199
+ * stringify( p, selection ); // '<p>[<b>foobar</b>]</p>'
34200
+ * ```
34201
+ *
34202
+ * If a range is placed inside the text node, it will be represented with `{` and `}`:
34203
+ *
34204
+ * ```ts
34205
+ * const text = downcastWriter.createText( 'foobar' );
34206
+ * const b = downcastWriter.createElement( 'b', null, text );
34207
+ * const p = downcastWriter.createElement( 'p', null, b );
34208
+ * const selection = downcastWriter.createSelection(
34209
+ * downcastWriter.createRange( downcastWriter.createPositionAt( text, 1 ), downcastWriter.createPositionAt( text, 5 ) )
34210
+ * );
34211
+ *
34212
+ * stringify( p, selection ); // '<p><b>f{ooba}r</b></p>'
34213
+ * ```
34214
+ *
34215
+ * ** Note: **
34216
+ * It is possible to unify selection markers to `[` and `]` for both (inside and outside text)
34217
+ * by setting the `sameSelectionCharacters=true` option. It is mainly used when the view stringify option is used by
34218
+ * model utilities.
34219
+ *
34220
+ * Multiple ranges are supported:
34221
+ *
34222
+ * ```ts
34223
+ * const text = downcastWriter.createText( 'foobar' );
34224
+ * const selection = downcastWriter.createSelection( [
34225
+ * downcastWriter.createRange( downcastWriter.createPositionAt( text, 0 ), downcastWriter.createPositionAt( text, 1 ) ),
34226
+ * downcastWriter.createRange( downcastWriter.createPositionAt( text, 3 ), downcastWriter.createPositionAt( text, 5 ) )
34227
+ * ] );
34228
+ *
34229
+ * stringify( text, selection ); // '{f}oo{ba}r'
34230
+ * ```
34231
+ *
34232
+ * A {@link module:engine/view/range~Range range} or {@link module:engine/view/position~Position position} instance can be provided
34233
+ * instead of the {@link module:engine/view/documentselection~DocumentSelection selection} instance. If a range instance
34234
+ * is provided, it will be converted to a selection containing this range. If a position instance is provided, it will
34235
+ * be converted to a selection containing one range collapsed at this position.
34236
+ *
34237
+ * ```ts
34238
+ * const text = downcastWriter.createText( 'foobar' );
34239
+ * const range = downcastWriter.createRange( downcastWriter.createPositionAt( text, 0 ), downcastWriter.createPositionAt( text, 1 ) );
34240
+ * const position = downcastWriter.createPositionAt( text, 3 );
34241
+ *
34242
+ * stringify( text, range ); // '{f}oobar'
34243
+ * stringify( text, position ); // 'foo{}bar'
34244
+ * ```
34245
+ *
34246
+ * An additional `options` object can be provided.
34247
+ * If `options.showType` is set to `true`, element's types will be
34248
+ * presented for {@link module:engine/view/attributeelement~AttributeElement attribute elements},
34249
+ * {@link module:engine/view/containerelement~ContainerElement container elements}
34250
+ * {@link module:engine/view/emptyelement~EmptyElement empty elements}
34251
+ * and {@link module:engine/view/uielement~UIElement UI elements}:
34252
+ *
34253
+ * ```ts
34254
+ * const attribute = downcastWriter.createAttributeElement( 'b' );
34255
+ * const container = downcastWriter.createContainerElement( 'p' );
34256
+ * const empty = downcastWriter.createEmptyElement( 'img' );
34257
+ * const ui = downcastWriter.createUIElement( 'span' );
34258
+ * getData( attribute, null, { showType: true } ); // '<attribute:b></attribute:b>'
34259
+ * getData( container, null, { showType: true } ); // '<container:p></container:p>'
34260
+ * getData( empty, null, { showType: true } ); // '<empty:img></empty:img>'
34261
+ * getData( ui, null, { showType: true } ); // '<ui:span></ui:span>'
34262
+ * ```
34263
+ *
34264
+ * If `options.showPriority` is set to `true`, a priority will be displayed for all
34265
+ * {@link module:engine/view/attributeelement~AttributeElement attribute elements}.
34266
+ *
34267
+ * ```ts
34268
+ * const attribute = downcastWriter.createAttributeElement( 'b' );
34269
+ * attribute._priority = 20;
34270
+ * getData( attribute, null, { showPriority: true } ); // <b view-priority="20"></b>
34271
+ * ```
34272
+ *
34273
+ * If `options.showAttributeElementId` is set to `true`, the attribute element's id will be displayed for all
34274
+ * {@link module:engine/view/attributeelement~AttributeElement attribute elements} that have it set.
34275
+ *
34276
+ * ```ts
34277
+ * const attribute = downcastWriter.createAttributeElement( 'span' );
34278
+ * attribute._id = 'marker:foo';
34279
+ * getData( attribute, null, { showAttributeElementId: true } ); // <span view-id="marker:foo"></span>
34280
+ * ```
34281
+ *
34282
+ * @param node The node to stringify.
34283
+ * @param selectionOrPositionOrRange A selection instance whose ranges will be included in the returned string data.
34284
+ * If a range instance is provided, it will be converted to a selection containing this range. If a position instance
34285
+ * is provided, it will be converted to a selection containing one range collapsed at this position.
34286
+ * @param options An object with additional options.
34287
+ * @param options.showType When set to `true`, the type of elements will be printed (`<container:p>`
34288
+ * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
34289
+ * @param options.showPriority When set to `true`, the attribute element's priority will be printed
34290
+ * (`<span view-priority="12">`, `<b view-priority="10">`).
34291
+ * @param options.showAttributeElementId When set to `true`, attribute element's id will be printed
34292
+ * (`<span id="marker:foo">`).
34293
+ * @param options.ignoreRoot When set to `true`, the root's element opening and closing will not be printed.
34294
+ * Mainly used by the `getData` function to ignore the {@link module:engine/view/document~Document document's} root element.
34295
+ * @param options.sameSelectionCharacters When set to `true`, the selection inside the text will be marked as
34296
+ * `{` and `}` and the selection outside the text as `[` and `]`. When set to `false`, both will be marked as `[` and `]` only.
34297
+ * @param options.renderUIElements When set to `true`, the inner content of each
34298
+ * {@link module:engine/view/uielement~UIElement} will be printed.
34299
+ * @param options.renderRawElements When set to `true`, the inner content of each
34300
+ * {@link module:engine/view/rawelement~RawElement} will be printed.
34301
+ * @param options.domConverter When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
34302
+ * instance, it lets the conversion go through exactly the same flow the editing view is going through,
34303
+ * i.e. with view data filtering. Otherwise the simple stub is used.
34304
+ * @returns An HTML-like string representing the view.
34305
+ */ function stringify$1(node, selectionOrPositionOrRange = null, options = {}) {
34306
+ let selection;
34307
+ if (selectionOrPositionOrRange instanceof Position$1 || selectionOrPositionOrRange instanceof Range$1) {
34308
+ selection = new DocumentSelection$1(selectionOrPositionOrRange);
34309
+ } else {
34310
+ selection = selectionOrPositionOrRange;
34311
+ }
34312
+ const viewStringify = new ViewStringify(node, selection, options);
34313
+ return viewStringify.stringify();
34314
+ }
34315
+ /**
34316
+ * Parses an HTML-like string and returns a view tree.
34317
+ * A simple string will be converted to a {@link module:engine/view/text~Text text} node:
34318
+ *
34319
+ * ```ts
34320
+ * parse( 'foobar' ); // Returns an instance of text.
34321
+ * ```
34322
+ *
34323
+ * {@link module:engine/view/element~Element Elements} will be parsed with attributes as children:
34324
+ *
34325
+ * ```ts
34326
+ * parse( '<b name="baz">foobar</b>' ); // Returns an instance of element with the `baz` attribute and a text child node.
34327
+ * ```
34328
+ *
34329
+ * Multiple nodes provided on root level will be converted to a
34330
+ * {@link module:engine/view/documentfragment~DocumentFragment document fragment}:
34331
+ *
34332
+ * ```ts
34333
+ * parse( '<b>foo</b><i>bar</i>' ); // Returns a document fragment with two child elements.
34334
+ * ```
34335
+ *
34336
+ * The method can parse multiple {@link module:engine/view/range~Range ranges} provided in string data and return a
34337
+ * {@link module:engine/view/documentselection~DocumentSelection selection} instance containing these ranges. Ranges placed inside
34338
+ * {@link module:engine/view/text~Text text} nodes should be marked using `{` and `}` brackets:
34339
+ *
34340
+ * ```ts
34341
+ * const { text, selection } = parse( 'f{ooba}r' );
34342
+ * ```
34343
+ *
34344
+ * Ranges placed outside text nodes should be marked using `[` and `]` brackets:
34345
+ *
34346
+ * ```ts
34347
+ * const { root, selection } = parse( '<p>[<b>foobar</b>]</p>' );
34348
+ * ```
34349
+ *
34350
+ * ** Note: **
34351
+ * It is possible to unify selection markers to `[` and `]` for both (inside and outside text)
34352
+ * by setting `sameSelectionCharacters=true` option. It is mainly used when the view parse option is used by model utilities.
34353
+ *
34354
+ * Sometimes there is a need for defining the order of ranges inside the created selection. This can be achieved by providing
34355
+ * the range order array as an additional parameter:
34356
+ *
34357
+ * ```ts
34358
+ * const { root, selection } = parse( '{fo}ob{ar}{ba}z', { order: [ 2, 3, 1 ] } );
34359
+ * ```
34360
+ *
34361
+ * In the example above, the first range (`{fo}`) will be added to the selection as the second one, the second range (`{ar}`) will be
34362
+ * added as the third and the third range (`{ba}`) will be added as the first one.
34363
+ *
34364
+ * If the selection's last range should be added as a backward one
34365
+ * (so the {@link module:engine/view/documentselection~DocumentSelection#anchor selection anchor} is represented
34366
+ * by the `end` position and {@link module:engine/view/documentselection~DocumentSelection#focus selection focus} is
34367
+ * represented by the `start` position), use the `lastRangeBackward` flag:
34368
+ *
34369
+ * ```ts
34370
+ * const { root, selection } = parse( `{foo}bar{baz}`, { lastRangeBackward: true } );
34371
+ * ```
34372
+ *
34373
+ * Some more examples and edge cases:
34374
+ *
34375
+ * ```ts
34376
+ * // Returns an empty document fragment.
34377
+ * parse( '' );
34378
+ *
34379
+ * // Returns an empty document fragment and a collapsed selection.
34380
+ * const { root, selection } = parse( '[]' );
34381
+ *
34382
+ * // Returns an element and a selection that is placed inside the document fragment containing that element.
34383
+ * const { root, selection } = parse( '[<a></a>]' );
34384
+ * ```
34385
+ *
34386
+ * @param data An HTML-like string to be parsed.
34387
+ * @param options.order An array with the order of parsed ranges added to the returned
34388
+ * {@link module:engine/view/documentselection~DocumentSelection Selection} instance. Each element should represent the
34389
+ * desired position of each range in the selection instance. For example: `[2, 3, 1]` means that the first range will be
34390
+ * placed as the second, the second as the third and the third as the first.
34391
+ * @param options.lastRangeBackward If set to `true`, the last range will be added as backward to the returned
34392
+ * {@link module:engine/view/documentselection~DocumentSelection selection} instance.
34393
+ * @param options.rootElement The default root to use when parsing elements.
34394
+ * When set to `null`, the root element will be created automatically. If set to
34395
+ * {@link module:engine/view/element~Element Element} or {@link module:engine/view/documentfragment~DocumentFragment DocumentFragment},
34396
+ * this node will be used as the root for all parsed nodes.
34397
+ * @param options.sameSelectionCharacters When set to `false`, the selection inside the text should be marked using
34398
+ * `{` and `}` and the selection outside the ext using `[` and `]`. When set to `true`, both should be marked with `[` and `]` only.
34399
+ * @param options.stylesProcessor Styles processor.
34400
+ * @returns Returns the parsed view node or an object with two fields: `view` and `selection` when selection ranges were included in the
34401
+ * data to parse.
34402
+ */ function parse$1(data, options = {}) {
34403
+ const viewDocument = new Document$1(new StylesProcessor());
34404
+ options.order = options.order || [];
34405
+ const rangeParser = new RangeParser({
34406
+ sameSelectionCharacters: options.sameSelectionCharacters
34407
+ });
34408
+ const processor = new XmlDataProcessor(viewDocument, {
34409
+ namespaces: Object.keys(allowedTypes)
34410
+ });
34411
+ // Convert data to view.
34412
+ let view = processor.toView(data);
34413
+ // At this point we have a view tree with Elements that could have names like `attribute:b:1`. In the next step
34414
+ // we need to parse Element's names and convert them to AttributeElements and ContainerElements.
34415
+ view = _convertViewElements(view);
34416
+ // If custom root is provided - move all nodes there.
34417
+ if (options.rootElement) {
34418
+ const root = options.rootElement;
34419
+ const nodes = view._removeChildren(0, view.childCount);
34420
+ root._removeChildren(0, root.childCount);
34421
+ root._appendChild(nodes);
34422
+ view = root;
34423
+ }
34424
+ // Parse ranges included in view text nodes.
34425
+ const ranges = rangeParser.parse(view, options.order);
34426
+ // If only one element is returned inside DocumentFragment - return that element.
34427
+ if (view.is('documentFragment') && view.childCount === 1) {
34428
+ view = view.getChild(0);
34429
+ }
34430
+ // When ranges are present - return object containing view, and selection.
34431
+ if (ranges.length) {
34432
+ const selection = new DocumentSelection$1(ranges, {
34433
+ backward: !!options.lastRangeBackward
34434
+ });
34435
+ return {
34436
+ view,
34437
+ selection
34438
+ };
34439
+ }
34440
+ // If single element is returned without selection - remove it from parent and return detached element.
34441
+ if (view.parent) {
34442
+ view._remove();
34443
+ }
34444
+ return view;
34445
+ }
34446
+ /**
34447
+ * Private helper class used for converting ranges represented as text inside view {@link module:engine/view/text~Text text nodes}.
34448
+ */ class RangeParser {
34449
+ /**
34450
+ * Parses the view and returns ranges represented inside {@link module:engine/view/text~Text text nodes}.
34451
+ * The method will remove all occurrences of `{`, `}`, `[` and `]` from found text nodes. If a text node is empty after
34452
+ * the process, it will be removed, too.
34453
+ *
34454
+ * @param node The starting node.
34455
+ * @param order The order of ranges. Each element should represent the desired position of the range after
34456
+ * 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
34457
+ * as the first.
34458
+ * @returns An array with ranges found.
34459
+ */ parse(node, order) {
34460
+ this._positions = [];
34461
+ // Remove all range brackets from view nodes and save their positions.
34462
+ this._getPositions(node);
34463
+ // Create ranges using gathered positions.
34464
+ let ranges = this._createRanges();
34465
+ // Sort ranges if needed.
34466
+ if (order.length) {
34467
+ if (order.length != ranges.length) {
34468
+ throw new Error(`Parse error - there are ${ranges.length} ranges found, but ranges order array contains ${order.length} elements.`);
34469
+ }
34470
+ ranges = this._sortRanges(ranges, order);
34471
+ }
34472
+ return ranges;
34473
+ }
34474
+ /**
34475
+ * Gathers positions of brackets inside the view tree starting from the provided node. The method will remove all occurrences of
34476
+ * `{`, `}`, `[` and `]` from found text nodes. If a text node is empty after the process, it will be removed, too.
34477
+ *
34478
+ * @param node Staring node.
34479
+ */ _getPositions(node) {
34480
+ if (node.is('documentFragment') || node.is('element')) {
34481
+ // Copy elements into the array, when nodes will be removed from parent node this array will still have all the
34482
+ // items needed for iteration.
34483
+ const children = [
34484
+ ...node.getChildren()
34485
+ ];
34486
+ for (const child of children){
34487
+ this._getPositions(child);
34488
+ }
34489
+ }
34490
+ if (node.is('$text')) {
34491
+ const regexp = new RegExp(`[${TEXT_RANGE_START_TOKEN}${TEXT_RANGE_END_TOKEN}\\${ELEMENT_RANGE_END_TOKEN}\\${ELEMENT_RANGE_START_TOKEN}]`, 'g');
34492
+ let text = node.data;
34493
+ let match;
34494
+ let offset = 0;
34495
+ const brackets = [];
34496
+ // Remove brackets from text and store info about offset inside text node.
34497
+ while(match = regexp.exec(text)){
34498
+ const index = match.index;
34499
+ const bracket = match[0];
34500
+ brackets.push({
34501
+ bracket,
34502
+ textOffset: index - offset
34503
+ });
34504
+ offset++;
34505
+ }
34506
+ text = text.replace(regexp, '');
34507
+ node._data = text;
34508
+ const index = node.index;
34509
+ const parent = node.parent;
34510
+ // Remove empty text nodes.
34511
+ if (!text) {
34512
+ node._remove();
34513
+ }
34514
+ for (const item of brackets){
34515
+ // Non-empty text node.
34516
+ if (text) {
34517
+ if (this.sameSelectionCharacters || !this.sameSelectionCharacters && (item.bracket == TEXT_RANGE_START_TOKEN || item.bracket == TEXT_RANGE_END_TOKEN)) {
34518
+ // Store information about text range delimiter.
34519
+ this._positions.push({
34520
+ bracket: item.bracket,
34521
+ position: new Position$1(node, item.textOffset)
34522
+ });
34523
+ } else {
34524
+ // Check if element range delimiter is not placed inside text node.
34525
+ if (!this.sameSelectionCharacters && item.textOffset !== 0 && item.textOffset !== text.length) {
34526
+ throw new Error(`Parse error - range delimiter '${item.bracket}' is placed inside text node.`);
34527
+ }
34528
+ // If bracket is placed at the end of the text node - it should be positioned after it.
34529
+ const offset = item.textOffset === 0 ? index : index + 1;
34530
+ // Store information about element range delimiter.
34531
+ this._positions.push({
34532
+ bracket: item.bracket,
34533
+ position: new Position$1(parent, offset)
34534
+ });
34535
+ }
34536
+ } else {
34537
+ if (!this.sameSelectionCharacters && item.bracket == TEXT_RANGE_START_TOKEN || item.bracket == TEXT_RANGE_END_TOKEN) {
34538
+ throw new Error(`Parse error - text range delimiter '${item.bracket}' is placed inside empty text node. `);
34539
+ }
34540
+ // Store information about element range delimiter.
34541
+ this._positions.push({
34542
+ bracket: item.bracket,
34543
+ position: new Position$1(parent, index)
34544
+ });
34545
+ }
34546
+ }
34547
+ }
34548
+ }
34549
+ /**
34550
+ * Sorts ranges in a given order. Range order should be an array and each element should represent the desired position
34551
+ * of the range after sorting.
34552
+ * For example: `[2, 3, 1]` means that the first range will be placed as the second, the second as the third and the third
34553
+ * as the first.
34554
+ *
34555
+ * @param ranges Ranges to sort.
34556
+ * @param rangesOrder An array with new range order.
34557
+ * @returns Sorted ranges array.
34558
+ */ _sortRanges(ranges, rangesOrder) {
34559
+ const sortedRanges = [];
34560
+ let index = 0;
34561
+ for (const newPosition of rangesOrder){
34562
+ if (ranges[newPosition - 1] === undefined) {
34563
+ throw new Error('Parse error - provided ranges order is invalid.');
34564
+ }
34565
+ sortedRanges[newPosition - 1] = ranges[index];
34566
+ index++;
34567
+ }
34568
+ return sortedRanges;
34569
+ }
34570
+ /**
34571
+ * Uses all found bracket positions to create ranges from them.
34572
+ */ _createRanges() {
34573
+ const ranges = [];
34574
+ let range = null;
34575
+ for (const item of this._positions){
34576
+ // When end of range is found without opening.
34577
+ if (!range && (item.bracket == ELEMENT_RANGE_END_TOKEN || item.bracket == TEXT_RANGE_END_TOKEN)) {
34578
+ throw new Error(`Parse error - end of range was found '${item.bracket}' but range was not started before.`);
34579
+ }
34580
+ // When second start of range is found when one is already opened - selection does not allow intersecting
34581
+ // ranges.
34582
+ if (range && (item.bracket == ELEMENT_RANGE_START_TOKEN || item.bracket == TEXT_RANGE_START_TOKEN)) {
34583
+ throw new Error(`Parse error - start of range was found '${item.bracket}' but one range is already started.`);
34584
+ }
34585
+ if (item.bracket == ELEMENT_RANGE_START_TOKEN || item.bracket == TEXT_RANGE_START_TOKEN) {
34586
+ range = new Range$1(item.position, item.position);
34587
+ } else {
34588
+ range.end = item.position;
34589
+ ranges.push(range);
34590
+ range = null;
34591
+ }
34592
+ }
34593
+ // Check if all ranges have proper ending.
34594
+ if (range !== null) {
34595
+ throw new Error('Parse error - range was started but no end delimiter was found.');
34596
+ }
34597
+ return ranges;
34598
+ }
34599
+ /**
34600
+ * Creates a range parser instance.
34601
+ *
34602
+ * @param options The range parser configuration.
34603
+ * @param options.sameSelectionCharacters When set to `true`, the selection inside the text is marked as
34604
+ * `{` and `}` and the selection outside the text as `[` and `]`. When set to `false`, both are marked as `[` and `]`.
34605
+ */ constructor(options){
34606
+ this.sameSelectionCharacters = !!options.sameSelectionCharacters;
34607
+ }
34608
+ }
34609
+ /**
34610
+ * Private helper class used for converting the view tree to a string.
34611
+ */ class ViewStringify {
34612
+ /**
34613
+ * Converts the view to a string.
34614
+ *
34615
+ * @returns String representation of the view elements.
34616
+ */ stringify() {
34617
+ let result = '';
34618
+ this._walkView(this.root, (chunk)=>{
34619
+ result += chunk;
34620
+ });
34621
+ return result;
34622
+ }
34623
+ /**
34624
+ * Executes a simple walker that iterates over all elements in the view tree starting from the root element.
34625
+ * Calls the `callback` with parsed chunks of string data.
34626
+ */ _walkView(root, callback) {
34627
+ const ignore = this.ignoreRoot && this.root === root;
34628
+ if (root.is('element') || root.is('documentFragment')) {
34629
+ if (root.is('element') && !ignore) {
34630
+ callback(this._stringifyElementOpen(root));
34631
+ }
34632
+ if (this.renderUIElements && root.is('uiElement')) {
34633
+ callback(root.render(document, this.domConverter).innerHTML);
34634
+ } else if (this.renderRawElements && root.is('rawElement')) {
34635
+ // There's no DOM element for "root" to pass to render(). Creating
34636
+ // a surrogate container to render the children instead.
34637
+ const rawContentContainer = document.createElement('div');
34638
+ root.render(rawContentContainer, this.domConverter);
34639
+ callback(rawContentContainer.innerHTML);
34640
+ } else {
34641
+ let offset = 0;
34642
+ callback(this._stringifyElementRanges(root, offset));
34643
+ for (const child of root.getChildren()){
34644
+ this._walkView(child, callback);
34645
+ offset++;
34646
+ callback(this._stringifyElementRanges(root, offset));
34647
+ }
34648
+ }
34649
+ if (root.is('element') && !ignore) {
34650
+ callback(this._stringifyElementClose(root));
34651
+ }
34652
+ }
34653
+ if (root.is('$text')) {
34654
+ callback(this._stringifyTextRanges(root));
34655
+ }
34656
+ }
34657
+ /**
34658
+ * Checks if a given {@link module:engine/view/element~Element element} has a {@link module:engine/view/range~Range#start range start}
34659
+ * or a {@link module:engine/view/range~Range#start range end} placed at a given offset and returns its string representation.
34660
+ */ _stringifyElementRanges(element, offset) {
34661
+ let start = '';
34662
+ let end = '';
34663
+ let collapsed = '';
34664
+ for (const range of this.ranges){
34665
+ if (range.start.parent == element && range.start.offset === offset) {
34666
+ if (range.isCollapsed) {
34667
+ collapsed += ELEMENT_RANGE_START_TOKEN + ELEMENT_RANGE_END_TOKEN;
34668
+ } else {
34669
+ start += ELEMENT_RANGE_START_TOKEN;
34670
+ }
34671
+ }
34672
+ if (range.end.parent === element && range.end.offset === offset && !range.isCollapsed) {
34673
+ end += ELEMENT_RANGE_END_TOKEN;
34674
+ }
34675
+ }
34676
+ return end + collapsed + start;
34677
+ }
34678
+ /**
34679
+ * Checks if a given {@link module:engine/view/element~Element Text node} has a
34680
+ * {@link module:engine/view/range~Range#start range start} or a
34681
+ * {@link module:engine/view/range~Range#start range end} placed somewhere inside. Returns a string representation of text
34682
+ * with range delimiters placed inside.
34683
+ */ _stringifyTextRanges(node) {
34684
+ const length = node.data.length;
34685
+ const data = node.data.split('');
34686
+ let rangeStartToken, rangeEndToken;
34687
+ if (this.sameSelectionCharacters) {
34688
+ rangeStartToken = ELEMENT_RANGE_START_TOKEN;
34689
+ rangeEndToken = ELEMENT_RANGE_END_TOKEN;
34690
+ } else {
34691
+ rangeStartToken = TEXT_RANGE_START_TOKEN;
34692
+ rangeEndToken = TEXT_RANGE_END_TOKEN;
34693
+ }
34694
+ // Add one more element for ranges ending after last character in text.
34695
+ data[length] = '';
34696
+ // Represent each letter as object with information about opening/closing ranges at each offset.
34697
+ const result = data.map((letter)=>{
34698
+ return {
34699
+ letter,
34700
+ start: '',
34701
+ end: '',
34702
+ collapsed: ''
34703
+ };
34704
+ });
34705
+ for (const range of this.ranges){
34706
+ const start = range.start;
34707
+ const end = range.end;
34708
+ if (start.parent == node && start.offset >= 0 && start.offset <= length) {
34709
+ if (range.isCollapsed) {
34710
+ result[end.offset].collapsed += rangeStartToken + rangeEndToken;
34711
+ } else {
34712
+ result[start.offset].start += rangeStartToken;
34713
+ }
34714
+ }
34715
+ if (end.parent == node && end.offset >= 0 && end.offset <= length && !range.isCollapsed) {
34716
+ result[end.offset].end += rangeEndToken;
34717
+ }
34718
+ }
34719
+ return result.map((item)=>item.end + item.collapsed + item.start + item.letter).join('');
34720
+ }
34721
+ /**
34722
+ * Converts the passed {@link module:engine/view/element~Element element} to an opening tag.
34723
+ *
34724
+ * Depending on the current configuration, the opening tag can be simple (`<a>`), contain a type prefix (`<container:p>`,
34725
+ * `<attribute:a>` or `<empty:img>`), contain priority information ( `<attribute:a view-priority="20">` ),
34726
+ * or contain element id ( `<attribute:span view-id="foo">` ). Element attributes will also be included
34727
+ * (`<a href="https://ckeditor.com" name="foobar">`).
34728
+ */ _stringifyElementOpen(element) {
34729
+ const priority = this._stringifyElementPriority(element);
34730
+ const id = this._stringifyElementId(element);
34731
+ const type = this._stringifyElementType(element);
34732
+ const name = [
34733
+ type,
34734
+ element.name
34735
+ ].filter((i)=>i !== '').join(':');
34736
+ const attributes = this._stringifyElementAttributes(element);
34737
+ const parts = [
34738
+ name,
34739
+ priority,
34740
+ id,
34741
+ attributes
34742
+ ];
34743
+ return `<${parts.filter((i)=>i !== '').join(' ')}>`;
34744
+ }
34745
+ /**
34746
+ * Converts the passed {@link module:engine/view/element~Element element} to a closing tag.
34747
+ * Depending on the current configuration, the closing tag can be simple (`</a>`) or contain a type prefix (`</container:p>`,
34748
+ * `</attribute:a>` or `</empty:img>`).
34749
+ */ _stringifyElementClose(element) {
34750
+ const type = this._stringifyElementType(element);
34751
+ const name = [
34752
+ type,
34753
+ element.name
34754
+ ].filter((i)=>i !== '').join(':');
34755
+ return `</${name}>`;
34756
+ }
34757
+ /**
34758
+ * Converts the passed {@link module:engine/view/element~Element element's} type to its string representation
34759
+ *
34760
+ * Returns:
34761
+ * * 'attribute' for {@link module:engine/view/attributeelement~AttributeElement attribute elements},
34762
+ * * 'container' for {@link module:engine/view/containerelement~ContainerElement container elements},
34763
+ * * 'empty' for {@link module:engine/view/emptyelement~EmptyElement empty elements},
34764
+ * * 'ui' for {@link module:engine/view/uielement~UIElement UI elements},
34765
+ * * 'raw' for {@link module:engine/view/rawelement~RawElement raw elements},
34766
+ * * an empty string when the current configuration is preventing showing elements' types.
34767
+ */ _stringifyElementType(element) {
34768
+ if (this.showType) {
34769
+ for(const type in allowedTypes){
34770
+ if (element instanceof allowedTypes[type]) {
34771
+ return type;
34772
+ }
34773
+ }
34774
+ }
34775
+ return '';
34776
+ }
34777
+ /**
34778
+ * Converts the passed {@link module:engine/view/element~Element element} to its priority representation.
34779
+ *
34780
+ * The priority string representation will be returned when the passed element is an instance of
34781
+ * {@link module:engine/view/attributeelement~AttributeElement attribute element} and the current configuration allows to show the
34782
+ * priority. Otherwise returns an empty string.
34783
+ */ _stringifyElementPriority(element) {
34784
+ if (this.showPriority && element.is('attributeElement')) {
34785
+ return `view-priority="${element.priority}"`;
34786
+ }
34787
+ return '';
34788
+ }
34789
+ /**
34790
+ * Converts the passed {@link module:engine/view/element~Element element} to its id representation.
34791
+ *
34792
+ * The id string representation will be returned when the passed element is an instance of
34793
+ * {@link module:engine/view/attributeelement~AttributeElement attribute element}, the element has an id
34794
+ * and the current configuration allows to show the id. Otherwise returns an empty string.
34795
+ */ _stringifyElementId(element) {
34796
+ if (this.showAttributeElementId && element.is('attributeElement') && element.id) {
34797
+ return `view-id="${element.id}"`;
34798
+ }
34799
+ return '';
34800
+ }
34801
+ /**
34802
+ * Converts the passed {@link module:engine/view/element~Element element} attributes to their string representation.
34803
+ * If an element has no attributes, an empty string is returned.
34804
+ */ _stringifyElementAttributes(element) {
34805
+ const attributes = [];
34806
+ const keys = [
34807
+ ...element.getAttributeKeys()
34808
+ ].sort();
34809
+ for (const attribute of keys){
34810
+ let attributeValue;
34811
+ if (attribute === 'class') {
34812
+ attributeValue = [
34813
+ ...element.getClassNames()
34814
+ ].sort().join(' ');
34815
+ } else if (attribute === 'style') {
34816
+ attributeValue = [
34817
+ ...element.getStyleNames()
34818
+ ].sort().map((style)=>`${style}:${element.getStyle(style).replace(/"/g, '&quot;')}`).join(';');
34819
+ } else {
34820
+ attributeValue = element.getAttribute(attribute);
34821
+ }
34822
+ attributes.push(`${attribute}="${attributeValue}"`);
34823
+ }
34824
+ return attributes.join(' ');
34825
+ }
34826
+ /**
34827
+ * Creates a view stringify instance.
34828
+ *
34829
+ * @param selection A selection whose ranges should also be converted to a string.
34830
+ * @param options An options object.
34831
+ * @param options.showType When set to `true`, the type of elements will be printed (`<container:p>`
34832
+ * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
34833
+ * @param options.showPriority When set to `true`, the attribute element's priority will be printed.
34834
+ * @param options.ignoreRoot When set to `true`, the root's element opening and closing tag will not
34835
+ * be outputted.
34836
+ * @param options.sameSelectionCharacters When set to `true`, the selection inside the text is marked as
34837
+ * `{` and `}` and the selection outside the text as `[` and `]`. When set to `false`, both are marked as `[` and `]`.
34838
+ * @param options.renderUIElements When set to `true`, the inner content of each
34839
+ * {@link module:engine/view/uielement~UIElement} will be printed.
34840
+ * @param options.renderRawElements When set to `true`, the inner content of each
34841
+ * @param options.domConverter When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
34842
+ * instance, it lets the conversion go through exactly the same flow the editing view is going through,
34843
+ * i.e. with view data filtering. Otherwise the simple stub is used.
34844
+ * {@link module:engine/view/rawelement~RawElement} will be printed.
34845
+ */ constructor(root, selection, options){
34846
+ this.root = root;
34847
+ this.selection = selection;
34848
+ this.ranges = [];
34849
+ if (selection) {
34850
+ this.ranges = [
34851
+ ...selection.getRanges()
34852
+ ];
34853
+ }
34854
+ this.showType = !!options.showType;
34855
+ this.showPriority = !!options.showPriority;
34856
+ this.showAttributeElementId = !!options.showAttributeElementId;
34857
+ this.ignoreRoot = !!options.ignoreRoot;
34858
+ this.sameSelectionCharacters = !!options.sameSelectionCharacters;
34859
+ this.renderUIElements = !!options.renderUIElements;
34860
+ this.renderRawElements = !!options.renderRawElements;
34861
+ this.domConverter = options.domConverter || domConverterStub;
34862
+ }
34863
+ }
34864
+ /**
34865
+ * Converts {@link module:engine/view/element~Element elements} to
34866
+ * {@link module:engine/view/attributeelement~AttributeElement attribute elements},
34867
+ * {@link module:engine/view/containerelement~ContainerElement container elements},
34868
+ * {@link module:engine/view/emptyelement~EmptyElement empty elements} or
34869
+ * {@link module:engine/view/uielement~UIElement UI elements}.
34870
+ * It converts the whole tree starting from the `rootNode`. The conversion is based on element names.
34871
+ * See the `_convertElement` method for more details.
34872
+ *
34873
+ * @param rootNode The root node to convert.
34874
+ * @returns The root node of converted elements.
34875
+ */ function _convertViewElements(rootNode) {
34876
+ if (rootNode.is('element') || rootNode.is('documentFragment')) {
34877
+ // Convert element or leave document fragment.
34878
+ const convertedElement = rootNode.is('documentFragment') ? new DocumentFragment$1(rootNode.document) : _convertElement(rootNode.document, rootNode);
34879
+ // Convert all child nodes.
34880
+ // Cache the nodes in array. Otherwise, we would skip some nodes because during iteration we move nodes
34881
+ // from `rootNode` to `convertedElement`. This would interfere with iteration.
34882
+ for (const child of [
34883
+ ...rootNode.getChildren()
34884
+ ]){
34885
+ if (convertedElement.is('emptyElement')) {
34886
+ throw new Error('Parse error - cannot parse inside EmptyElement.');
34887
+ } else if (convertedElement.is('uiElement')) {
34888
+ throw new Error('Parse error - cannot parse inside UIElement.');
34889
+ } else if (convertedElement.is('rawElement')) {
34890
+ throw new Error('Parse error - cannot parse inside RawElement.');
34891
+ }
34892
+ convertedElement._appendChild(_convertViewElements(child));
34893
+ }
34894
+ return convertedElement;
34895
+ }
34896
+ return rootNode;
34897
+ }
34898
+ /**
34899
+ * Converts an {@link module:engine/view/element~Element element} to
34900
+ * {@link module:engine/view/attributeelement~AttributeElement attribute element},
34901
+ * {@link module:engine/view/containerelement~ContainerElement container element},
34902
+ * {@link module:engine/view/emptyelement~EmptyElement empty element} or
34903
+ * {@link module:engine/view/uielement~UIElement UI element}.
34904
+ * If the element's name is in the format of `attribute:b`, it will be converted to
34905
+ * an {@link module:engine/view/attributeelement~AttributeElement attribute element} with a priority of 11.
34906
+ * Additionally, attribute elements may have specified priority (for example `view-priority="11"`) and/or
34907
+ * id (for example `view-id="foo"`).
34908
+ * If the element's name is in the format of `container:p`, it will be converted to
34909
+ * a {@link module:engine/view/containerelement~ContainerElement container element}.
34910
+ * If the element's name is in the format of `empty:img`, it will be converted to
34911
+ * an {@link module:engine/view/emptyelement~EmptyElement empty element}.
34912
+ * If the element's name is in the format of `ui:span`, it will be converted to
34913
+ * a {@link module:engine/view/uielement~UIElement UI element}.
34914
+ * If the element's name does not contain any additional information, a {@link module:engine/view/element~Element view Element} will be
34915
+ * returned.
34916
+ *
34917
+ * @param viewElement A view element to convert.
34918
+ * @returns A tree view element converted according to its name.
34919
+ */ function _convertElement(viewDocument, viewElement) {
34920
+ const info = _convertElementNameAndInfo(viewElement);
34921
+ const ElementConstructor = allowedTypes[info.type];
34922
+ const newElement = ElementConstructor ? new ElementConstructor(viewDocument, info.name) : new Element$1(viewDocument, info.name);
34923
+ if (newElement.is('attributeElement')) {
34924
+ if (info.priority !== null) {
34925
+ newElement._priority = info.priority;
34926
+ }
34927
+ if (info.id !== null) {
34928
+ newElement._id = info.id;
34929
+ }
34930
+ }
34931
+ // Move attributes.
34932
+ for (const attributeKey of viewElement.getAttributeKeys()){
34933
+ newElement._setAttribute(attributeKey, viewElement.getAttribute(attributeKey));
34934
+ }
34935
+ return newElement;
34936
+ }
34937
+ /**
34938
+ * Converts the `view-priority` attribute and the {@link module:engine/view/element~Element#name element's name} information needed for
34939
+ * creating {@link module:engine/view/attributeelement~AttributeElement attribute element},
34940
+ * {@link module:engine/view/containerelement~ContainerElement container element},
34941
+ * {@link module:engine/view/emptyelement~EmptyElement empty element} or
34942
+ * {@link module:engine/view/uielement~UIElement UI element}.
34943
+ * The name can be provided in two formats: as a simple element's name (`div`), or as a type and name (`container:div`,
34944
+ * `attribute:span`, `empty:img`, `ui:span`);
34945
+ *
34946
+ * @param viewElement The element whose name should be converted.
34947
+ * @returns An object with parsed information:
34948
+ * * `name` The parsed name of the element.
34949
+ * * `type` The parsed type of the element. It can be `attribute`, `container` or `empty`.
34950
+ * * `priority` The parsed priority of the element.
34951
+ */ function _convertElementNameAndInfo(viewElement) {
34952
+ const parts = viewElement.name.split(':');
34953
+ const priority = _convertPriority(viewElement.getAttribute('view-priority'));
34954
+ const id = viewElement.hasAttribute('view-id') ? viewElement.getAttribute('view-id') : null;
34955
+ viewElement._removeAttribute('view-priority');
34956
+ viewElement._removeAttribute('view-id');
34957
+ if (parts.length == 1) {
34958
+ return {
34959
+ name: parts[0],
34960
+ type: priority !== null ? 'attribute' : null,
34961
+ priority,
34962
+ id
34963
+ };
34964
+ }
34965
+ // Check if type and name: container:div.
34966
+ const type = _convertType(parts[0]);
34967
+ if (type) {
34968
+ return {
34969
+ name: parts[1],
34970
+ type,
34971
+ priority,
34972
+ id
34973
+ };
34974
+ }
34975
+ throw new Error(`Parse error - cannot parse element's name: ${viewElement.name}.`);
34976
+ }
34977
+ /**
34978
+ * Checks if the element's type is allowed. Returns `attribute`, `container`, `empty` or `null`.
34979
+ */ function _convertType(type) {
34980
+ return type in allowedTypes ? type : null;
34981
+ }
34982
+ /**
34983
+ * Checks if a given priority is allowed. Returns null if the priority cannot be converted.
34984
+ */ function _convertPriority(priorityString) {
34985
+ const priority = parseInt(priorityString, 10);
34986
+ if (!isNaN(priority)) {
34987
+ return priority;
34988
+ }
34989
+ return null;
34990
+ }
34991
+
34992
+ /**
34993
+ * Writes the content of a model {@link module:engine/model/document~Document document} to an HTML-like string.
34994
+ *
34995
+ * ```ts
34996
+ * getData( editor.model ); // -> '<paragraph>Foo![]</paragraph>'
34997
+ * ```
34998
+ *
34999
+ * **Note:** A {@link module:engine/model/text~Text text} node that contains attributes will be represented as:
35000
+ *
35001
+ * ```xml
35002
+ * <$text attribute="value">Text data</$text>
35003
+ * ```
35004
+ *
35005
+ * **Note:** Using this tool in production-grade code is not recommended. It was designed for development, prototyping,
35006
+ * debugging and testing.
35007
+ *
35008
+ * @param options.withoutSelection Whether to write the selection. When set to `true`, the selection will
35009
+ * not be included in the returned string.
35010
+ * @param options.rootName The name of the root from which the data should be stringified. If not provided,
35011
+ * the default `main` name will be used.
35012
+ * @param options.convertMarkers Whether to include markers in the returned string.
35013
+ * @returns The stringified data.
35014
+ */ function getData(model, options = {}) {
35015
+ if (!(model instanceof Model)) {
35016
+ throw new TypeError('Model needs to be an instance of module:engine/model/model~Model.');
35017
+ }
35018
+ const rootName = options.rootName || 'main';
35019
+ const root = model.document.getRoot(rootName);
35020
+ return getData._stringify(root, options.withoutSelection ? null : model.document.selection, options.convertMarkers ? model.markers : null);
35021
+ }
35022
+ // Set stringify as getData private method - needed for testing/spying.
35023
+ getData._stringify = stringify;
35024
+ /**
35025
+ * Sets the content of a model {@link module:engine/model/document~Document document} provided as an HTML-like string.
35026
+ *
35027
+ * ```ts
35028
+ * setData( editor.model, '<paragraph>Foo![]</paragraph>' );
35029
+ * ```
35030
+ *
35031
+ * **Note:** Remember to register elements in the {@link module:engine/model/model~Model#schema model's schema} before
35032
+ * trying to use them.
35033
+ *
35034
+ * **Note:** To create a {@link module:engine/model/text~Text text} node that contains attributes use:
35035
+ *
35036
+ * ```xml
35037
+ * <$text attribute="value">Text data</$text>
35038
+ * ```
35039
+ *
35040
+ * **Note:** Using this tool in production-grade code is not recommended. It was designed for development, prototyping,
35041
+ * debugging and testing.
35042
+ *
35043
+ * @param data HTML-like string to write into the document.
35044
+ * @param options.rootName Root name where parsed data will be stored. If not provided, the default `main`
35045
+ * name will be used.
35046
+ * @param options.selectionAttributes A list of attributes which will be passed to the selection.
35047
+ * @param options.lastRangeBackward If set to `true`, the last range will be added as backward.
35048
+ * @param options.batchType Batch type used for inserting elements. See {@link module:engine/model/batch~Batch#constructor}.
35049
+ * See {@link module:engine/model/batch~Batch#type}.
35050
+ */ function setData(model, data, options = {}) {
35051
+ if (!(model instanceof Model)) {
35052
+ throw new TypeError('Model needs to be an instance of module:engine/model/model~Model.');
35053
+ }
35054
+ let modelDocumentFragment;
35055
+ let selection = null;
35056
+ const modelRoot = model.document.getRoot(options.rootName || 'main');
35057
+ // Parse data string to model.
35058
+ const parsedResult = setData._parse(data, model.schema, {
35059
+ lastRangeBackward: options.lastRangeBackward,
35060
+ selectionAttributes: options.selectionAttributes,
35061
+ context: [
35062
+ modelRoot.name
35063
+ ]
35064
+ });
35065
+ // Retrieve DocumentFragment and Selection from parsed model.
35066
+ if ('model' in parsedResult) {
35067
+ modelDocumentFragment = parsedResult.model;
35068
+ selection = parsedResult.selection;
35069
+ } else {
35070
+ modelDocumentFragment = parsedResult;
35071
+ }
35072
+ if (options.batchType !== undefined) {
35073
+ model.enqueueChange(options.batchType, writeToModel);
35074
+ } else {
35075
+ model.change(writeToModel);
35076
+ }
35077
+ function writeToModel(writer) {
35078
+ // Replace existing model in document by new one.
35079
+ writer.remove(writer.createRangeIn(modelRoot));
35080
+ writer.insert(modelDocumentFragment, modelRoot);
35081
+ // Clean up previous document selection.
35082
+ writer.setSelection(null);
35083
+ writer.removeSelectionAttribute(model.document.selection.getAttributeKeys());
35084
+ // Update document selection if specified.
35085
+ if (selection) {
35086
+ const ranges = [];
35087
+ for (const range of selection.getRanges()){
35088
+ const start = new Position(modelRoot, range.start.path);
35089
+ const end = new Position(modelRoot, range.end.path);
35090
+ ranges.push(new Range(start, end));
35091
+ }
35092
+ writer.setSelection(ranges, {
35093
+ backward: selection.isBackward
35094
+ });
35095
+ if (options.selectionAttributes) {
35096
+ writer.setSelectionAttribute(selection.getAttributes());
35097
+ }
35098
+ }
35099
+ }
35100
+ }
35101
+ // Set parse as setData private method - needed for testing/spying.
35102
+ setData._parse = parse;
35103
+ /**
35104
+ * Converts model nodes to HTML-like string representation.
35105
+ *
35106
+ * **Note:** A {@link module:engine/model/text~Text text} node that contains attributes will be represented as:
35107
+ *
35108
+ * ```xml
35109
+ * <$text attribute="value">Text data</$text>
35110
+ * ```
35111
+ *
35112
+ * @param node A node to stringify.
35113
+ * @param selectionOrPositionOrRange A selection instance whose ranges will be included in the returned string data.
35114
+ * If a range instance is provided, it will be converted to a selection containing this range. If a position instance
35115
+ * is provided, it will be converted to a selection containing one range collapsed at this position.
35116
+ * @param markers Markers to include.
35117
+ * @returns An HTML-like string representing the model.
35118
+ */ function stringify(node, selectionOrPositionOrRange = null, markers = null) {
35119
+ const model = new Model();
35120
+ const mapper = new Mapper();
35121
+ let selection = null;
35122
+ let range;
35123
+ // Create a range witch wraps passed node.
35124
+ if (node instanceof RootElement || node instanceof DocumentFragment) {
35125
+ range = model.createRangeIn(node);
35126
+ } else {
35127
+ // Node is detached - create new document fragment.
35128
+ if (!node.parent) {
35129
+ const fragment = new DocumentFragment(node);
35130
+ range = model.createRangeIn(fragment);
35131
+ } else {
35132
+ range = new Range(model.createPositionBefore(node), model.createPositionAfter(node));
35133
+ }
35134
+ }
35135
+ // Get selection from passed selection or position or range if at least one is specified.
35136
+ if (selectionOrPositionOrRange instanceof Selection) {
35137
+ selection = selectionOrPositionOrRange;
35138
+ } else if (selectionOrPositionOrRange instanceof DocumentSelection) {
35139
+ selection = selectionOrPositionOrRange;
35140
+ } else if (selectionOrPositionOrRange instanceof Range) {
35141
+ selection = new Selection(selectionOrPositionOrRange);
35142
+ } else if (selectionOrPositionOrRange instanceof Position) {
35143
+ selection = new Selection(selectionOrPositionOrRange);
35144
+ }
35145
+ // Set up conversion.
35146
+ // Create a temporary view controller.
35147
+ const stylesProcessor = new StylesProcessor();
35148
+ const view = new View(stylesProcessor);
35149
+ const viewDocument = view.document;
35150
+ const viewRoot = new RootEditableElement(viewDocument, 'div');
35151
+ // Create a temporary root element in view document.
35152
+ viewRoot.rootName = 'main';
35153
+ viewDocument.roots.add(viewRoot);
35154
+ // Create and setup downcast dispatcher.
35155
+ const downcastDispatcher = new DowncastDispatcher({
35156
+ mapper,
35157
+ schema: model.schema
35158
+ });
35159
+ // Bind root elements.
35160
+ mapper.bindElements(node.root, viewRoot);
35161
+ downcastDispatcher.on('insert:$text', insertText());
35162
+ downcastDispatcher.on('insert', insertAttributesAndChildren(), {
35163
+ priority: 'lowest'
35164
+ });
35165
+ downcastDispatcher.on('attribute', (evt, data, conversionApi)=>{
35166
+ if (data.item instanceof Selection || data.item instanceof DocumentSelection || data.item.is('$textProxy')) {
35167
+ const converter = wrap((modelAttributeValue, { writer })=>{
35168
+ return writer.createAttributeElement('model-text-with-attributes', {
35169
+ [data.attributeKey]: stringifyAttributeValue(modelAttributeValue)
35170
+ });
35171
+ });
35172
+ converter(evt, data, conversionApi);
35173
+ }
35174
+ });
35175
+ downcastDispatcher.on('insert', insertElement((modelItem)=>{
35176
+ // Stringify object types values for properly display as an output string.
35177
+ const attributes = convertAttributes(modelItem.getAttributes(), stringifyAttributeValue);
35178
+ return new ContainerElement(viewDocument, modelItem.name, attributes);
35179
+ }));
35180
+ downcastDispatcher.on('selection', convertRangeSelection());
35181
+ downcastDispatcher.on('selection', convertCollapsedSelection());
35182
+ downcastDispatcher.on('addMarker', insertUIElement((data, { writer })=>{
35183
+ const name = data.markerName + ':' + (data.isOpening ? 'start' : 'end');
35184
+ return writer.createUIElement(name);
35185
+ }));
35186
+ const markersMap = new Map();
35187
+ if (markers) {
35188
+ // To provide stable results, sort markers by name.
35189
+ for (const marker of Array.from(markers).sort((a, b)=>a.name < b.name ? 1 : -1)){
35190
+ markersMap.set(marker.name, marker.getRange());
35191
+ }
35192
+ }
35193
+ // Convert model to view.
35194
+ const writer = view._writer;
35195
+ downcastDispatcher.convert(range, markersMap, writer);
35196
+ // Convert model selection to view selection.
35197
+ if (selection) {
35198
+ downcastDispatcher.convertSelection(selection, markers || model.markers, writer);
35199
+ }
35200
+ // Parse view to data string.
35201
+ let data = stringify$1(viewRoot, viewDocument.selection, {
35202
+ sameSelectionCharacters: true
35203
+ });
35204
+ // Removing unnecessary <div> and </div> added because `viewRoot` was also stringified alongside input data.
35205
+ data = data.substr(5, data.length - 11);
35206
+ view.destroy();
35207
+ // Replace valid XML `model-text-with-attributes` element name to `$text`.
35208
+ return data.replace(new RegExp('model-text-with-attributes', 'g'), '$text');
35209
+ }
35210
+ /**
35211
+ * Parses an HTML-like string and returns the model {@link module:engine/model/rootelement~RootElement rootElement}.
35212
+ *
35213
+ * **Note:** To create a {@link module:engine/model/text~Text text} node that contains attributes use:
35214
+ *
35215
+ * ```xml
35216
+ * <$text attribute="value">Text data</$text>
35217
+ * ```
35218
+ *
35219
+ * @param data HTML-like string to be parsed.
35220
+ * @param schema A schema instance used by converters for element validation.
35221
+ * @param options Additional configuration.
35222
+ * @param options.selectionAttributes A list of attributes which will be passed to the selection.
35223
+ * @param options.lastRangeBackward If set to `true`, the last range will be added as backward.
35224
+ * @param options.context The conversion context. If not provided, the default `'$root'` will be used.
35225
+ * @returns Returns the parsed model node or an object with two fields: `model` and `selection`,
35226
+ * when selection ranges were included in the data to parse.
35227
+ */ function parse(data, schema, options = {}) {
35228
+ const mapper = new Mapper();
35229
+ // Replace not accepted by XML `$text` tag name by valid one `model-text-with-attributes`.
35230
+ data = data.replace(new RegExp('\\$text', 'g'), 'model-text-with-attributes');
35231
+ // Parse data to view using view utils.
35232
+ const parsedResult = parse$1(data, {
35233
+ sameSelectionCharacters: true,
35234
+ lastRangeBackward: !!options.lastRangeBackward
35235
+ });
35236
+ // Retrieve DocumentFragment and Selection from parsed view.
35237
+ let viewDocumentFragment;
35238
+ let viewSelection = null;
35239
+ let selection = null;
35240
+ if ('view' in parsedResult && 'selection' in parsedResult) {
35241
+ viewDocumentFragment = parsedResult.view;
35242
+ viewSelection = parsedResult.selection;
35243
+ } else {
35244
+ viewDocumentFragment = parsedResult;
35245
+ }
35246
+ // Set up upcast dispatcher.
35247
+ const modelController = new Model();
35248
+ const upcastDispatcher = new UpcastDispatcher({
35249
+ schema
35250
+ });
35251
+ upcastDispatcher.on('documentFragment', convertToModelFragment(mapper));
35252
+ upcastDispatcher.on('element:model-text-with-attributes', convertToModelText());
35253
+ upcastDispatcher.on('element', convertToModelElement(mapper));
35254
+ upcastDispatcher.on('text', convertToModelText());
35255
+ // Convert view to model.
35256
+ let model = modelController.change((writer)=>upcastDispatcher.convert(viewDocumentFragment.root, writer, options.context || '$root'));
35257
+ mapper.bindElements(model, viewDocumentFragment.root);
35258
+ // If root DocumentFragment contains only one element - return that element.
35259
+ if (model.childCount == 1) {
35260
+ model = model.getChild(0);
35261
+ }
35262
+ // Convert view selection to model selection.
35263
+ if (viewSelection) {
35264
+ const ranges = [];
35265
+ // Convert ranges.
35266
+ for (const viewRange of viewSelection.getRanges()){
35267
+ ranges.push(mapper.toModelRange(viewRange));
35268
+ }
35269
+ // Create new selection.
35270
+ selection = new Selection(ranges, {
35271
+ backward: viewSelection.isBackward
35272
+ });
35273
+ // Set attributes to selection if specified.
35274
+ for (const [key, value] of toMap(options.selectionAttributes || [])){
35275
+ selection.setAttribute(key, value);
35276
+ }
35277
+ }
35278
+ // Return model end selection when selection was specified.
35279
+ if (selection) {
35280
+ return {
35281
+ model,
35282
+ selection
35283
+ };
35284
+ }
35285
+ // Otherwise return model only.
35286
+ return model;
35287
+ }
35288
+ // -- Converters view -> model -----------------------------------------------------
35289
+ function convertToModelFragment(mapper) {
35290
+ return (evt, data, conversionApi)=>{
35291
+ const childrenResult = conversionApi.convertChildren(data.viewItem, data.modelCursor);
35292
+ mapper.bindElements(data.modelCursor.parent, data.viewItem);
35293
+ data = Object.assign(data, childrenResult);
35294
+ evt.stop();
35295
+ };
35296
+ }
35297
+ function convertToModelElement(mapper) {
35298
+ return (evt, data, conversionApi)=>{
35299
+ const elementName = data.viewItem.name;
35300
+ if (!conversionApi.schema.checkChild(data.modelCursor, elementName)) {
35301
+ throw new Error(`Element '${elementName}' was not allowed in given position.`);
35302
+ }
35303
+ // View attribute value is a string so we want to typecast it to the original type.
35304
+ // E.g. `bold="true"` - value will be parsed from string `"true"` to boolean `true`.
35305
+ const attributes = convertAttributes(data.viewItem.getAttributes(), parseAttributeValue);
35306
+ const element = conversionApi.writer.createElement(data.viewItem.name, attributes);
35307
+ conversionApi.writer.insert(element, data.modelCursor);
35308
+ mapper.bindElements(element, data.viewItem);
35309
+ conversionApi.convertChildren(data.viewItem, element);
35310
+ data.modelRange = Range._createOn(element);
35311
+ data.modelCursor = data.modelRange.end;
35312
+ evt.stop();
35313
+ };
35314
+ }
35315
+ function convertToModelText() {
35316
+ return (evt, data, conversionApi)=>{
35317
+ if (!conversionApi.schema.checkChild(data.modelCursor, '$text')) {
35318
+ throw new Error('Text was not allowed in given position.');
35319
+ }
35320
+ let node;
35321
+ if (data.viewItem.is('element')) {
35322
+ // View attribute value is a string so we want to typecast it to the original type.
35323
+ // E.g. `bold="true"` - value will be parsed from string `"true"` to boolean `true`.
35324
+ const attributes = convertAttributes(data.viewItem.getAttributes(), parseAttributeValue);
35325
+ const viewText = data.viewItem.getChild(0);
35326
+ node = conversionApi.writer.createText(viewText.data, attributes);
35327
+ } else {
35328
+ node = conversionApi.writer.createText(data.viewItem.data);
35329
+ }
35330
+ conversionApi.writer.insert(node, data.modelCursor);
35331
+ data.modelRange = Range._createFromPositionAndShift(data.modelCursor, node.offsetSize);
35332
+ data.modelCursor = data.modelRange.end;
35333
+ evt.stop();
35334
+ };
35335
+ }
35336
+ // Tries to get original type of attribute value using JSON parsing:
35337
+ //
35338
+ // `'true'` => `true`
35339
+ // `'1'` => `1`
35340
+ // `'{"x":1,"y":2}'` => `{ x: 1, y: 2 }`
35341
+ //
35342
+ // Parse error means that value should be a string:
35343
+ //
35344
+ // `'foobar'` => `'foobar'`
35345
+ function parseAttributeValue(attribute) {
35346
+ try {
35347
+ return JSON.parse(attribute);
35348
+ } catch (e) {
35349
+ return attribute;
35350
+ }
35351
+ }
35352
+ // When value is an Object stringify it.
35353
+ function stringifyAttributeValue(data) {
35354
+ if (isPlainObject(data)) {
35355
+ return JSON.stringify(data);
35356
+ }
35357
+ return data;
35358
+ }
35359
+ // Loop trough attributes map and converts each value by passed converter.
35360
+ function* convertAttributes(attributes, converter) {
35361
+ for (const [key, value] of attributes){
35362
+ yield [
35363
+ key,
35364
+ converter(value)
35365
+ ];
35366
+ }
35367
+ }
35368
+
35369
+ export { AttributeElement, AttributeOperation, BubblingEventInfo, ClickObserver, Conversion, DataController, DataTransfer, DocumentFragment, DocumentSelection, DomConverter, DomEventData, DomEventObserver, DowncastWriter, EditingController, View as EditingView, Element, FocusObserver, History, HtmlDataProcessor, InsertOperation, LivePosition, LiveRange, MarkerOperation, Matcher, MergeOperation, Model, MouseObserver, MoveOperation, NoOperation, Observer, OperationFactory, Position, Range, RenameOperation, Renderer, RootAttributeOperation, RootOperation, SplitOperation, StylesMap, StylesProcessor, TabObserver, Text, TextProxy, TreeWalker, UpcastWriter, AttributeElement as ViewAttributeElement, ContainerElement as ViewContainerElement, Document$1 as ViewDocument, DocumentFragment$1 as ViewDocumentFragment, EditableElement as ViewEditableElement, Element$1 as ViewElement, EmptyElement as ViewEmptyElement, RawElement as ViewRawElement, RootEditableElement as ViewRootEditableElement, Text$1 as ViewText, TreeWalker$1 as ViewTreeWalker, UIElement as ViewUIElement, getData as _getModelData, getData$1 as _getViewData, parse as _parseModel, parse$1 as _parseView, setData as _setModelData, setData$1 as _setViewData, stringify as _stringifyModel, stringify$1 as _stringifyView, addBackgroundRules, addBorderRules, addMarginRules, addPaddingRules, disablePlaceholder, enablePlaceholder, getBoxSidesShorthandValue, getBoxSidesValueReducer, getBoxSidesValues, getFillerOffset$4 as getFillerOffset, getPositionShorthandNormalizer, getShorthandValues, hidePlaceholder, isAttachment, isColor, isLength, isLineStyle, isPercentage, isPosition, isRepeat, isURL, needsPlaceholder, showPlaceholder, transformSets };
33973
35370
  //# sourceMappingURL=index.js.map