Repository: maxGraph/maxGraph
Pull request #423 (2024-05-27 07:34:23)

fix: set correct type for XmlCanvas2D.root
The type of the `root` property had been incorrectly set to `SVGElement` during the Typescript migration (see commit 413796ad).
This prevented to use it in various scenario, like the example described in the `ExportImage` JSDoc.

This example was working in mxGraph and typed-mxgraph declares the property as a `Element`, so it is now declares the property as a `Element`

A test has been added to show that the `ExportImage` example using XmlCanvas2D with a root using an Element type works.

## Notes

- typed-mxgraph: https://github.com/typed-mxgraph/typed-mxgraph/blob/187dd4f0dc7644c0cfbc998dae5fc90879597d81/lib/util/mxXmlCanvas2D.d.ts#L2-L8

- mxGraph documentation for the usage of `XmlCanvas2D` in `ExportImage`: https://github.com/jgraph/mxgraph/blob/ff141aab158417bd866e2dfebd06c61d40773cd2/javascript/src/js/util/mxImageExport.js#L13-L25




Patch:
diff --git a/packages/core/src/util/xmlUtils.ts b/packages/core/src/util/xmlUtils.ts
index 9dc3b2aaa..6a71785a4 100644
--- a/packages/core/src/util/xmlUtils.ts
+++ b/packages/core/src/util/xmlUtils.ts
@@ -108,8 +108,7 @@ export const getViewXml = (
  * no linefeed is defined.
  *
  * @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 linefeeds are converted into. Default is `\&#xa;`.
  */
 export const getXml = (node: Element, linefeed = '&#xa;'): string => {
   const xmlSerializer = new XMLSerializer();
diff --git a/packages/core/src/view/canvas/XmlCanvas2D.ts b/packages/core/src/view/canvas/XmlCanvas2D.ts
index 2b49b8f66..0c77a5bcb 100644
--- a/packages/core/src/view/canvas/XmlCanvas2D.ts
+++ b/packages/core/src/view/canvas/XmlCanvas2D.ts
@@ -42,7 +42,7 @@ import { DirectionValue, TextDirectionValue } from '../../types';
  *   <setFontFamily>, <setFontStyle>
  * - <setShadow>, <setShadowColor>, <setShadowAlpha>, <setShadowOffset>
  * - <rect>, <roundrect>, <ellipse>, <image>, <text>
- * - <begin>, {@link oveTo}, <lineTo>, <quadTo>, <curveTo>
+ * - <begin>, {@link moveTo}, <lineTo>, <quadTo>, <curveTo>
  * - <stroke>, <fill>, <fillAndStroke>
  *
  * <AbstractCanvas2D.arcTo> is an additional method for drawing paths. This is
@@ -53,8 +53,8 @@ import { DirectionValue, TextDirectionValue } from '../../types';
  *
  * Constructs a new abstract canvas.
  */
-class mxXmlCanvas2D extends AbstractCanvas2D {
-  constructor(root: SVGElement) {
+class XmlCanvas2D extends AbstractCanvas2D {
+  constructor(root: Element) {
     super();
 
     this.root = root;
@@ -66,7 +66,7 @@ class mxXmlCanvas2D extends AbstractCanvas2D {
   /**
    * Reference to the container for the SVG content.
    */
-  root: SVGElement;
+  root: Element;
 
   /**
    * Specifies if text output should be enabled.
@@ -991,4 +991,4 @@ class mxXmlCanvas2D extends AbstractCanvas2D {
   }
 }
 
-export default mxXmlCanvas2D;
+export default XmlCanvas2D;
diff --git a/packages/core/src/view/image/ImageExport.ts b/packages/core/src/view/image/ImageExport.ts
index 4f2d05721..7786a8734 100644
--- a/packages/core/src/view/image/ImageExport.ts
+++ b/packages/core/src/view/image/ImageExport.ts
@@ -22,30 +22,29 @@ import Shape from '../geometry/Shape';
 import { Graph } from '../Graph';
 
 /**
- * Creates a new image export instance to be used with an export canvas. Here
- * is an example that uses this class to create an image via a backend using
- * {@link XmlExportCanvas}.
+ * Creates a new image export instance to be used with an export canvas.
+ *
+ * Here is an example that uses this class to create an image via a backend using {@link XmlCanvas2D}.
  *
  * ```javascript
- * var xmlDoc = mxUtils.createXmlDocument();
- * var root = xmlDoc.createElement('output');
+ * const xmlDoc = xmlUtils.createXmlDocument();
+ * const root = xmlDoc.createElement('output');
  * xmlDoc.appendChild(root);
  *
- * var xmlCanvas = new mxXmlCanvas2D(root);
- * var imgExport = new mxImageExport();
+ * const xmlCanvas = new XmlCanvas2D(root);
+ * const imgExport = new ImageExport();
+ *
  * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
+ * const xml = xmlUtils.getXml(root);
  *
- * var bounds = graph.getGraphBounds();
- * var w = Math.ceil(bounds.x + bounds.width);
- * var h = Math.ceil(bounds.y + bounds.height);
+ * const bounds = graph.getGraphBounds();
+ * const w = Math.ceil(bounds.x + bounds.width);
+ * const h = Math.ceil(bounds.y + bounds.height);
  *
- * var xml = mxUtils.getXml(root);
  * new MaxXmlRequest('export', 'format=png&w=' + w +
  * 		'&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))
  * 		.simulate(document, '_blank');
  * ```
- *
- * @class ImageExport
  */
 class ImageExport {
   /**


Test patch:
diff --git a/packages/core/__tests__/view/image/ImageExport.test.ts b/packages/core/__tests__/view/image/ImageExport.test.ts
new file mode 100644
index 000000000..1ab461434
--- /dev/null
+++ b/packages/core/__tests__/view/image/ImageExport.test.ts
@@ -0,0 +1,71 @@
+/*
+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 { expect, test } from '@jest/globals';
+import { ImageExport, Point, XmlCanvas2D } from '../../../src';
+import { createXmlDocument, getPrettyXml } from '../../../src/util/xmlUtils';
+import { createGraphWithoutContainer } from '../../utils';
+
+test('export with XmlCanvas2D', () => {
+  const graph = createGraphWithoutContainer();
+  const parent = graph.getDefaultParent();
+
+  graph.batchUpdate(() => {
+    const v1 = graph.insertVertex({
+      parent,
+      id: 'v1',
+      value: 'vertex 1',
+      position: [0, 0],
+      size: [80, 30],
+    });
+    const v2 = graph.insertVertex({
+      parent,
+      id: 'v2',
+      value: 'vertex 2',
+      position: [100, 100],
+      size: [80, 30],
+    });
+
+    graph.insertEdge(parent, null, '', v1, v2);
+    const e1 = graph.insertEdge({
+      parent,
+      id: 'e1',
+      value: 'edge 1',
+      source: v1,
+      target: v2,
+    });
+    e1.geometry!.points = [new Point(50, 50)];
+  });
+
+  const xmlDoc = createXmlDocument();
+  const root = xmlDoc.createElement('data');
+  xmlDoc.appendChild(root);
+
+  const xmlCanvas = new XmlCanvas2D(root);
+  const imgExport = new ImageExport();
+
+  imgExport.drawState(graph.getView().getState(graph.model.root!)!, xmlCanvas);
+  const xml = getPrettyXml(root);
+
+  expect(xml).toBe(`<data>
+  <fontfamily family="Arial,Helvetica" />
+  <fontsize size="11" />
+  <shadowcolor color="gray" />
+  <shadowalpha alpha="1" />
+  <shadowoffset dx="2" dy="3" />
+</data>
+`);
+});