Repository: maxGraph/maxGraph
Pull request #471 (2024-06-18 05:37:34)

feat: accept `Element` parameter in `ModelXmlSerializer.import`
`Editor.writeGraphModel` can now use `ModelXmlSerializer`, eliminating the need to manually register model codecs. `Editor.readGraphModel` also uses `ModelXmlSerializer` for the same reason.

`ModelXmlSerializer.import` now produces a more compact xml when `pretty` is `false` (no extra spaces).



Patch:
diff --git a/package-lock.json b/package-lock.json
index 970c58a19..1ae23fe17 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7947,11 +7947,11 @@
       }
     },
     "node_modules/braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "dependencies": {
-        "fill-range": "^7.0.1"
+        "fill-range": "^7.1.1"
       },
       "engines": {
         "node": ">=8"
@@ -11470,9 +11470,9 @@
       }
     },
     "node_modules/fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "dependencies": {
         "to-regex-range": "^5.0.1"
       },
@@ -21184,9 +21184,9 @@
       }
     },
     "node_modules/puppeteer-core/node_modules/ws": {
-      "version": "6.2.2",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
-      "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
+      "version": "6.2.3",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
+      "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
       "dev": true,
       "dependencies": {
         "async-limiter": "~1.0.0"
@@ -25618,9 +25618,9 @@
       }
     },
     "node_modules/webpack-bundle-analyzer/node_modules/ws": {
-      "version": "7.5.9",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
-      "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "version": "7.5.10",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
+      "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
       "engines": {
         "node": ">=8.3.0"
       },
@@ -26200,9 +26200,9 @@
       }
     },
     "node_modules/ws": {
-      "version": "8.16.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
-      "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
       "engines": {
         "node": ">=10.0.0"
       },
diff --git a/packages/core/src/editor/Editor.ts b/packages/core/src/editor/Editor.ts
index 95ce7f57c..6cdd3c553 100644
--- a/packages/core/src/editor/Editor.ts
+++ b/packages/core/src/editor/Editor.ts
@@ -28,6 +28,7 @@ import StackLayout from '../view/layout/StackLayout';
 import EventObject from '../view/event/EventObject';
 import { getOffset } from '../util/styleUtils';
 import Codec from '../serialization/Codec';
+import { ModelXmlSerializer } from '../serialization/ModelXmlSerializer';
 import MaxWindow, { error } from '../gui/MaxWindow';
 import MaxForm from '../gui/MaxForm';
 import Outline from '../view/other/Outline';
@@ -56,6 +57,8 @@ import { show } from '../util/printUtils';
 import PanningHandler from '../view/handler/PanningHandler';
 import { cloneCell } from '../util/cellArrayUtils';
 
+// TODO disabled side effects, so editor resources are not loaded by default
+// This should be done in a different way
 /**
  * Installs the required language resources at class
  * loading time.
@@ -207,9 +210,9 @@ if (mxLoadResources) {
  *
  * ```javascript
  * <Task label="Task" description="">
- *   <mxCell vertex="true">
- *     <mxGeometry as="geometry" width="72" height="32"/>
- *   </mxCell>
+ *   <Cell vertex="true">
+ *     <Geometry as="geometry" width="72" height="32"/>
+ *   </Cell>
  * </Task>
  * ```
  *
@@ -257,7 +260,7 @@ if (mxLoadResources) {
  * New entries can be added to the toolbar by inserting an add-node into the
  * above configuration. Existing entries may be removed and changed by
  * modifying or removing the respective entries in the configuration.
- * The configuration is read by the {@link DefaultPopupMenuCodec}, the format of the
+ * The configuration is read by the {@link EditorPopupMenuCodec}, the format of the
  * configuration is explained in {@link EditorPopupMenu.decode}.
  *
  * The toolbar is defined in the EditorToolbar section. Items can be added
@@ -271,8 +274,7 @@ if (mxLoadResources) {
  *     ...
  * ```
  *
- * The format of the configuration is described in
- * {@link DefaultToolbarCodec.decode}.
+ * The format of the configuration is described in {@link EditorToolbarCodec.decode}.
  *
  * Ids:
  *
@@ -281,12 +283,12 @@ if (mxLoadResources) {
  * time. For example, if the Task node from above has an id attribute, then
  * the {@link Cell.id} of the corresponding cell will have this value. If there
  * is no Id collision in the model, then the cell may be retrieved using this
- * Id with the {@link mxGraphModel.getCell} function. If there is a collision, a new
- * Id will be created for the cell using {@link mxGraphModel.createId}. At encoding
+ * Id with the {@link GraphDataModel.getCell} function. If there is a collision, a new
+ * Id will be created for the cell using {@link GraphDataModel.createId}. At encoding
  * time, this new Id will replace the value previously stored under the id
  * attribute in the Task node.
  *
- * See {@link EditorCodec}, {@link DefaultToolbarCodec} and {@link DefaultPopupMenuCodec}
+ * See {@link EditorCodec}, {@link EditorToolbarCodec} and {@link EditorPopupMenuCodec}
  * for information about configuring the editor and user interface.
  *
  * Programmatically inserting cells:
@@ -1891,13 +1893,11 @@ export class Editor extends EventSource {
   }
 
   /**
-   * Reads the specified XML node into the existing graph model and resets
-   * the command history and modified state.
-   * @param node
+   * Reads the specified XML node into the existing graph model and resets the command history and modified state.
+   * @param node the XML node to be read into the graph model.
    */
-  readGraphModel(node: any): void {
-    const dec = new Codec(node.ownerDocument);
-    dec.decode(node, this.graph.getDataModel());
+  readGraphModel(node: Element): void {
+    new ModelXmlSerializer(this.graph.getDataModel()).import(node);
     this.resetHistory();
   }
 
@@ -1974,24 +1974,14 @@ export class Editor extends EventSource {
   }
 
   /**
-   * Hook to create the string representation of the diagram. The default
-   * implementation uses an {@link Codec} to encode the graph model as
-   * follows:
+   * Hook to create the string representation of the diagram.
    *
-   * @example
-   * ```javascript
-   * var enc = new Codec();
-   * var node = enc.encode(this.graph.getDataModel());
-   * return mxUtils.getXml(node, this.linefeed);
-   * ```
+   * The default implementation uses {@link ModelXmlSerializer} to encode the graph model.
    *
    * @param linefeed Optional character to be used as the linefeed. Default is {@link linefeed}.
    */
-  writeGraphModel(linefeed: string): string {
-    linefeed = linefeed != null ? linefeed : this.linefeed;
-    const enc = new Codec();
-    const node = <Element>enc.encode(this.graph.getDataModel());
-    return getXml(node, linefeed);
+  writeGraphModel(linefeed?: string): string {
+    return new ModelXmlSerializer(this.graph.getDataModel()).export({ pretty: false });
   }
 
   /**
diff --git a/packages/core/src/editor/EditorKeyHandler.ts b/packages/core/src/editor/EditorKeyHandler.ts
index 381ccd382..8b234faaf 100644
--- a/packages/core/src/editor/EditorKeyHandler.ts
+++ b/packages/core/src/editor/EditorKeyHandler.ts
@@ -22,7 +22,7 @@ import KeyHandler from '../view/handler/KeyHandler';
 import Editor from './Editor';
 
 /**
- * Binds keycodes to actionnames in an editor.  This aggregates an internal {@link handler} and extends the implementation of {@link KeyHandler.escape} to not only cancel the editing, but also hide the properties dialog and fire an <Editor.escape> event via {@link editor}.  An instance of this class is created by {@link Editor} and stored in {@link Editor.keyHandler}.
+ * Binds keycodes to action names in an editor.  This aggregates an internal {@link handler} and extends the implementation of {@link KeyHandler.escape} to not only cancel the editing, but also hide the properties dialog and fire an <Editor.escape> event via {@link editor}.  An instance of this class is created by {@link Editor} and stored in {@link Editor.keyHandler}.
  *
  * @Example
  * Bind the delete key to the delete action in an existing editor.
diff --git a/packages/core/src/serialization/ModelXmlSerializer.ts b/packages/core/src/serialization/ModelXmlSerializer.ts
index 4c12eee25..64976eea0 100644
--- a/packages/core/src/serialization/ModelXmlSerializer.ts
+++ b/packages/core/src/serialization/ModelXmlSerializer.ts
@@ -15,7 +15,7 @@ limitations under the License.
 */
 
 import { registerModelCodecs } from './register';
-import { getPrettyXml, parseXml } from '../util/xmlUtils';
+import { getPrettyXml, getXml, parseXml } from '../util/xmlUtils';
 import Codec from './Codec';
 import type GraphDataModel from '../view/GraphDataModel';
 
@@ -52,16 +52,14 @@ export class ModelXmlSerializer {
     this.registerCodecs();
   }
 
-  import(xml: string): void {
-    const doc = parseXml(xml);
+  import(input: string | Element): void {
+    const doc = typeof input === 'string' ? parseXml(input) : input.ownerDocument;
     new Codec(doc).decode(doc.documentElement, this.dataModel);
   }
 
   export(options?: ModelExportOptions): string {
     const encodedNode = new Codec().encode(this.dataModel);
-    return options?.pretty ?? true
-      ? getPrettyXml(encodedNode)
-      : getPrettyXml(encodedNode, '', '', '');
+    return options?.pretty ?? true ? getPrettyXml(encodedNode) : getXml(encodedNode!);
   }
 
   /**
diff --git a/packages/core/src/serialization/register.ts b/packages/core/src/serialization/register.ts
index 6394f8e63..7c69c768c 100644
--- a/packages/core/src/serialization/register.ts
+++ b/packages/core/src/serialization/register.ts
@@ -77,6 +77,7 @@ let isModelCodecsRegistered = false;
  * @param force if `true` register the codecs even if they were already registered. If false, only register them
  *              if they have never been registered before.
  * @since 0.10.0
+ * @category Configuration
  */
 export const registerModelCodecs = (force = false) => {
   if (!isModelCodecsRegistered || force) {
@@ -113,6 +114,7 @@ let isCoreCodecsRegistered = false;
  * @param force if `true` register the codecs even if they were already registered. If false, only register them
  *              if they have never been registered before.
  * @since 0.6.0
+ * @category Configuration
  */
 export const registerCoreCodecs = (force = false) => {
   if (!isCoreCodecsRegistered || force) {
@@ -136,6 +138,7 @@ let isEditorCodecsRegistered = false;
  * @param force if `true` register the codecs even if they were already registered. If false, only register them
  *              if they have never been registered before.
  * @since 0.6.0
+ * @category Configuration
  */
 export const registerEditorCodecs = (force = false) => {
   if (!isEditorCodecsRegistered || force) {
@@ -154,6 +157,7 @@ export const registerEditorCodecs = (force = false) => {
  * @param force if `true` register the codecs even if they were already registered. If false, only register them
  *              if they have never been registered before.
  * @since 0.6.0
+ * @category Configuration
  */
 export const registerAllCodecs = (force = false) => {
   registerCoreCodecs(force);
diff --git a/packages/core/src/util/StringUtils.ts b/packages/core/src/util/StringUtils.ts
index 79983da14..f54d07daa 100644
--- a/packages/core/src/util/StringUtils.ts
+++ b/packages/core/src/util/StringUtils.ts
@@ -132,8 +132,8 @@ export const removeWhitespace = (node: HTMLElement, before: boolean) => {
  * Replaces characters (less than, greater than, newlines and quotes) with
  * their HTML entities in the given string and returns the result.
  *
- * @param {string} s String that contains the characters to be converted.
- * @param {boolean} newline If newlines should be replaced. Default is true.
+ * @param s String that contains the characters to be converted.
+ * @param newline If newlines should be replaced. Default is `true`.
  */
 export const htmlEntities = (s: string, newline = true): string => {
   s = String(s || '');
diff --git a/packages/core/src/util/xmlUtils.ts b/packages/core/src/util/xmlUtils.ts
index 6a71785a4..f128e49e4 100644
--- a/packages/core/src/util/xmlUtils.ts
+++ b/packages/core/src/util/xmlUtils.ts
@@ -102,20 +102,18 @@ export const getViewXml = (
 };
 
 /**
- * Returns the XML content of the specified node. For Internet Explorer,
- * all \r\n\t[\t]* are removed from the XML string and the remaining \r\n
- * are replaced by \n. All \n are then replaced with linefeed, or &#xa; if
- * no linefeed is defined.
+ * Returns the XML content of the specified node.
+ *
+ * All `\n` are then replaced with the linefeed parameter value.
  *
  * @param node DOM node to return the XML for.
- * @param linefeed Optional string that linefeeds are converted into. Default is `\&#xa;`.
+ * @param linefeed Optional string that linefeed are converted into. Default is `&#xa;`.
  */
 export const getXml = (node: Element, linefeed = '&#xa;'): string => {
   const xmlSerializer = new XMLSerializer();
   let xml = xmlSerializer.serializeToString(node);
 
-  // Replaces linefeeds with HTML Entities.
-  linefeed = linefeed || '&#xa;';
+  // Replaces linefeed with HTML Entities.
   xml = xml.replace(/\n/g, linefeed);
   return xml;
 };
diff --git a/packages/core/src/view/cell/register-shapes.ts b/packages/core/src/view/cell/register-shapes.ts
index 988d8aa1b..4ab8056cf 100644
--- a/packages/core/src/view/cell/register-shapes.ts
+++ b/packages/core/src/view/cell/register-shapes.ts
@@ -38,6 +38,8 @@ let isDefaultElementsRegistered = false;
 
 /**
  * Add default shapes into `CellRenderer` shapes.
+ *
+ * @category Configuration
  */
 export function registerDefaultShapes() {
   if (!isDefaultElementsRegistered) {
diff --git a/packages/core/src/view/geometry/edge/MarkerShape.ts b/packages/core/src/view/geometry/edge/MarkerShape.ts
index c1e6f8d7f..25fffef3b 100644
--- a/packages/core/src/view/geometry/edge/MarkerShape.ts
+++ b/packages/core/src/view/geometry/edge/MarkerShape.ts
@@ -252,6 +252,9 @@ function diamond(
 }
 
 let isDefaultMarkersRegistered = false;
+/**
+ * @category Configuration
+ */
 export const registerDefaultEdgeMarkers = (): void => {
   if (!isDefaultMarkersRegistered) {
     MarkerShape.addMarker('classic', createArrow(2));
diff --git a/packages/core/src/view/mixins/CellsMixin.ts b/packages/core/src/view/mixins/CellsMixin.ts
index 5aae41fb9..81cbc3075 100644
--- a/packages/core/src/view/mixins/CellsMixin.ts
+++ b/packages/core/src/view/mixins/CellsMixin.ts
@@ -214,8 +214,8 @@ declare module '../Graph' {
      */
     toggleCellStyles: (
       key: keyof CellStateStyle,
-      defaultValue: boolean,
-      cells: Cell[]
+      defaultValue?: boolean,
+      cells?: Cell[]
     ) => boolean | null;
 
     /**
diff --git a/packages/core/src/view/style/register.ts b/packages/core/src/view/style/register.ts
index 8bce72778..9d25672f0 100644
--- a/packages/core/src/view/style/register.ts
+++ b/packages/core/src/view/style/register.ts
@@ -23,6 +23,8 @@ let isDefaultsRegistered = false;
 
 /**
  * Register style elements for "EdgeStyle" and "Perimeters".
+ *
+ * @category Configuration
  */
 export const registerDefaultStyleElements = (): void => {
   if (!isDefaultsRegistered) {


Test patch:
diff --git a/packages/core/__tests__/editor/Editor.test.ts b/packages/core/__tests__/editor/Editor.test.ts
new file mode 100644
index 000000000..e6c6c761b
--- /dev/null
+++ b/packages/core/__tests__/editor/Editor.test.ts
@@ -0,0 +1,100 @@
+/*
+Copyright 2024-present The maxGraph project Contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { describe, expect, test } from '@jest/globals';
+import { Editor, Geometry } from '../../src';
+import { ModelChecker } from '../serialization/utils';
+import { parseXml } from '../../src/util/xmlUtils';
+
+describe('writeGraphModel', () => {
+  test('empty model', () => {
+    const editor = new Editor(null!);
+
+    const xml = editor.writeGraphModel();
+    expect(xml).toBe(
+      '<GraphDataModel><root><Cell id="0"><Object as="style"/></Cell><Cell id="1" parent="0"><Object as="style"/></Cell></root></GraphDataModel>'
+    );
+  });
+
+  test('non empty model, write with default linefeed', () => {
+    const editor = new Editor(null!);
+    editor.graph.insertVertex({
+      id: 'v1',
+      value: 'vertex 1\nwith linefeed',
+      position: [10, 20],
+      size: [30, 30],
+    });
+
+    const xml = editor.writeGraphModel();
+    expect(xml).toBe(
+      '<GraphDataModel><root><Cell id="0"><Object as="style"/></Cell><Cell id="1" parent="0"><Object as="style"/></Cell><Cell id="v1" value="vertex 1&#xA;with linefeed" vertex="1" parent="1"><Geometry _x="10" _y="20" _width="30" _height="30" as="geometry"/><Object as="style"/></Cell></root></GraphDataModel>'
+    );
+  });
+
+  test('non empty model, write with custom linefeed', () => {
+    const editor = new Editor(null!);
+    editor.graph.insertVertex({
+      id: 'v1',
+      value: 'vertex 1\nwith linefeed\nand others\nagain',
+      position: [0, 0],
+      size: [20, 30],
+    });
+
+    // The linefeed parameter should be removed from the method signature
+    // In maxGraph, the getXml function ignores the linefeed as it always uses XmlSerializer
+    // In mxGraph, it was only taken into account in Internet Explorer which used a custom serialization code
+    // The test here has been written prior switching the writeGraphModel implementation from direct Codec usage to ModelXmlSerializer
+    const xml = editor.writeGraphModel('LF');
+    expect(xml).toBe(
+      '<GraphDataModel><root><Cell id="0"><Object as="style"/></Cell><Cell id="1" parent="0"><Object as="style"/></Cell><Cell id="v1" value="vertex 1&#xA;with linefeed&#xA;and others&#xA;again" vertex="1" parent="1"><Geometry _width="20" _height="30" as="geometry"/><Object as="style"/></Cell></root></GraphDataModel>'
+    );
+  });
+});
+
+test('readGraphModel', () => {
+  const editor = new Editor(null!);
+
+  const xmlDocument = parseXml(
+    `<GraphDataModel>
+  <root>
+    <Cell id="0">
+      <Object as="style" />
+    </Cell>
+    <Cell id="1" parent="0">
+      <Object as="style" />
+    </Cell>
+    <Cell id="v1" value="vertex 1" vertex="1" parent="1">
+      <Geometry _x="100" _y="100" _width="100" _height="80" as="geometry" />
+      <Object fillColor="green" strokeWidth="4" as="style" />
+    </Cell>
+  </root>
+</GraphDataModel>`
+  );
+
+  editor.readGraphModel(xmlDocument.documentElement);
+  const model = editor.graph.model;
+
+  const modelChecker = new ModelChecker(model);
+  modelChecker.checkRootCells();
+
+  modelChecker.expectIsVertex(model.getCell('v1'), 'vertex 1', {
+    geometry: new Geometry(100, 100, 100, 80),
+    style: {
+      fillColor: 'green',
+      strokeWidth: 4,
+    },
+  });
+});
diff --git a/packages/core/__tests__/serialization/serialization.xml.mxGraph.test.ts b/packages/core/__tests__/serialization/serialization.xml.mxGraph.test.ts
index 18829e58a..52e12b308 100644
--- a/packages/core/__tests__/serialization/serialization.xml.mxGraph.test.ts
+++ b/packages/core/__tests__/serialization/serialization.xml.mxGraph.test.ts
@@ -16,13 +16,7 @@ limitations under the License.
 
 import { describe, test } from '@jest/globals';
 import { ModelChecker } from './utils';
-import {
-  CodecRegistry,
-  Geometry,
-  GraphDataModel,
-  ModelXmlSerializer,
-  Point,
-} from '../../src';
+import { Geometry, GraphDataModel, ModelXmlSerializer, Point } from '../../src';
 
 describe('import mxGraph model', () => {
   test('Model with geometry', () => {
diff --git a/packages/core/__tests__/serialization/serialization.xml.test.ts b/packages/core/__tests__/serialization/serialization.xml.test.ts
index f2bfa0ec4..1f58e217a 100644
--- a/packages/core/__tests__/serialization/serialization.xml.test.ts
+++ b/packages/core/__tests__/serialization/serialization.xml.test.ts
@@ -177,7 +177,7 @@ describe('export', () => {
     expect(
       new ModelXmlSerializer(new GraphDataModel()).export({ pretty: false })
     ).toEqual(
-      `<GraphDataModel><root><Cell id="0"><Object as="style" /></Cell><Cell id="1" parent="0"><Object as="style" /></Cell></root></GraphDataModel>`
+      `<GraphDataModel><root><Cell id="0"><Object as="style"/></Cell><Cell id="1" parent="0"><Object as="style"/></Cell></root></GraphDataModel>`
     );
   });