diff --git a/packages/mindplot/package.json b/packages/mindplot/package.json index bed09a1d..6fc065e3 100644 --- a/packages/mindplot/package.json +++ b/packages/mindplot/package.json @@ -28,7 +28,8 @@ "cy:run": "cypress run", "test:unit": "jest ./test/unit/export/*.ts ./test/unit/layout/*.js", "test:integration": "start-server-and-test playground http-get://localhost:8083 cy:run", - "test": "yarn test:unit && yarn test:integration" + "test": "yarn test:unit && yarn test:integration", + "generate": "ts-node ./src/components/export/freemind/generatedClasses.ts" }, "private": false, "dependencies": { @@ -69,6 +70,7 @@ "start-server-and-test": "^1.14.0", "ts-jest": "^27.1.2", "ts-loader": "^9.2.6", + "ts-node": "^10.4.0", "webpack": "^5.44.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^4.7.2", diff --git a/packages/mindplot/src/components/export/FreemindExporter.ts b/packages/mindplot/src/components/export/FreemindExporter.ts new file mode 100644 index 00000000..cac92947 --- /dev/null +++ b/packages/mindplot/src/components/export/FreemindExporter.ts @@ -0,0 +1,302 @@ +import { Mindmap } from '../..'; +import INodeModel, { TopicShape } from '../model/INodeModel'; +import RelationshipModel from '../model/RelationshipModel'; +import IconModel from '../model/IconModel'; +import FeatureModel from '../model/FeatureModel'; +import LinkModel from '../model/LinkModel'; +import NoteModel from '../model/NoteModel'; +import Exporter from './Exporter'; +import FreemindConstant from './freemind/FreemindConstant'; +import VersionNumber from './freemind/importer/VersionNumber'; +import ObjectFactory from './freemind/ObjectFactory'; +import FreemindMap from './freemind/Map'; +import FreeminNode, { Choise } from './freemind/Node'; +import Arrowlink from './freemind/Arrowlink'; +import Richcontent from './freemind/Richcontent'; +import Icon from './freemind/Icon'; +import Edge from './freemind/Edge'; +import Font from './freemind/Font'; + +type PositionNodeType = {x: number, y: number} + +class FreemindExporter implements Exporter { + private mindmap: Mindmap; + + private nodeMap: Map = null; + + private version: VersionNumber = FreemindConstant.SUPPORTED_FREEMIND_VERSION; + + private objectFactory: ObjectFactory; + + private static wisweToFreeFontSize: Map = new Map(); + + constructor(mindmap: Mindmap) { + this.mindmap = mindmap; + } + + static { + this.wisweToFreeFontSize.set(6, 10); + this.wisweToFreeFontSize.set(8, 12); + this.wisweToFreeFontSize.set(10, 18); + this.wisweToFreeFontSize.set(15, 24); + } + + private static parserXMLString(xmlStr: string, mimeType: DOMParserSupportedType): Document { + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(xmlStr, mimeType); + + // Is there any parsing error ?. + if (xmlDoc.getElementsByTagName('parsererror').length > 0) { + const xmmStr = new XMLSerializer().serializeToString(xmlDoc); + console.log(xmmStr); + throw new Error(`Unexpected error parsing: ${xmlStr}. Error: ${xmmStr}`); + } + + return xmlDoc; + } + + extension(): string { + return 'mm'; + } + + async export(): Promise { + this.objectFactory = new ObjectFactory(); + this.nodeMap = new Map(); + + const freemainMap: FreemindMap = this.objectFactory.createMap(); + freemainMap.setVesion(this.getVersionNumber()); + + const main: FreeminNode = this.objectFactory.createNode(); + freemainMap.setNode(main); + + const centralTopic: INodeModel = this.mindmap.getCentralTopic(); + + if (centralTopic) { + this.nodeMap.set(centralTopic.getId(), main); + this.setTopicPropertiesToNode(main, centralTopic, true); + this.addNodeFromTopic(centralTopic, main); + } + + const relationships: Array = this.mindmap.getRelationships(); + relationships.forEach((relationship: RelationshipModel) => { + const srcNode: FreeminNode = this.nodeMap.get(relationship.getFromNode()); + const destNode: FreeminNode = this.nodeMap.get(relationship.getToNode()); + + if (srcNode && destNode) { + const arrowlink: Arrowlink = this.objectFactory.crateArrowlink(); + + arrowlink.setDestination(destNode.getId()); + + if (relationship.getEndArrow() && relationship.getEndArrow()) arrowlink.setEndarrow('Default'); + + if (relationship.getStartArrow() && relationship.getStartArrow()) arrowlink.setStartarrow('Default'); + + const cloudEdge: Array = srcNode.getArrowlinkOrCloudOrEdge(); + + cloudEdge.push(arrowlink); + } + }); + + const freeToXml = freemainMap.toXml(); + const xmlToString = new XMLSerializer().serializeToString(freeToXml); + + return Promise.resolve(xmlToString); + } + + private addNodeFromTopic(mainTopic: INodeModel, destNode: FreeminNode): void { + const curretnTopics: Array = mainTopic.getChildren(); + + curretnTopics.forEach((currentTopic: INodeModel) => { + const newNode: FreeminNode = this.objectFactory.createNode(); + this.nodeMap.set(currentTopic.getId(), newNode); + + this.setTopicPropertiesToNode(newNode, currentTopic, false); + + destNode.getArrowlinkOrCloudOrEdge().push(newNode); + + this.addNodeFromTopic(currentTopic, newNode); + + const position: PositionNodeType = currentTopic.getPosition(); + if (position) { + const xPos: number = position.x; + newNode.setPosition((xPos < 0 ? 'left' : 'right')); + } else newNode.setPosition('left'); + }); + } + + private setTopicPropertiesToNode(freemindNode: FreeminNode, mindmapTopic: INodeModel, isRoot: boolean): void { + freemindNode.setId(`ID_${mindmapTopic.getId()}`); + + const text = mindmapTopic.getText(); + + if (!text.includes('\n')) { + freemindNode.setText(text); + } else { + const richcontent: Richcontent = this.buildRichcontent(text, 'NODE'); + freemindNode.getArrowlinkOrCloudOrEdge().push(richcontent); + } + + const wiseShape: string = mindmapTopic.getShapeType(); + if (wiseShape && TopicShape.LINE !== wiseShape) { + freemindNode.setBackgorundColor(this.rgbToHex(mindmapTopic.getBackgroundColor())); + } + + if (wiseShape && !wiseShape) { + const isRootRoundedRectangle = isRoot && TopicShape.ROUNDED_RECT !== wiseShape; + const notIsRootLine = !isRoot && TopicShape.LINE !== wiseShape; + + if (isRootRoundedRectangle || notIsRootLine) { + let style: string = wiseShape; + if (TopicShape.ROUNDED_RECT === style || TopicShape.ELLIPSE === style) { + style = 'bubble'; + } + freemindNode.setStyle(style); + } + } else if (!isRoot) freemindNode.setStyle('fork'); + + this.addFeautreNode(freemindNode, mindmapTopic); + this.addFontNode(freemindNode, mindmapTopic); + this.addEdgeNode(freemindNode, mindmapTopic); + } + + private buildRichcontent(text: string, type: string): Richcontent { + const richconent: Richcontent = this.objectFactory.createRichcontent(); + + richconent.setType(type); + let html = ''; + + text.split('\n').forEach((line: string) => { + html += `

${line.trim()}

`; + }); + + html += ''; + + const richconentDocument: Document = FreemindExporter.parserXMLString(html, 'application/xml'); + const xmlResult = new XMLSerializer().serializeToString(richconentDocument); + richconent.setHtml(xmlResult); + + return richconent; + } + + private addFeautreNode(freemindNode: FreeminNode, mindmapTopic: INodeModel): void { + const branches: Array = mindmapTopic.getChildren(); + const freemindIcon: Icon = new Icon(); + + branches + .filter((node: INodeModel) => node.getText !== undefined) + .forEach((node: INodeModel) => { + node.getFeatures().forEach((feature: FeatureModel) => { + const type = feature.getType(); + + if (type === 'link') { + const link = feature as LinkModel; + freemindNode.setLink(link); + } + + if (type === 'note') { + const note = feature as NoteModel; + const richcontent: Richcontent = this.buildRichcontent(note.getText(), 'NOTE'); + freemindNode.getArrowlinkOrCloudOrEdge().push(richcontent); + } + + if (type === 'icon') { + const icon = feature as IconModel; + freemindIcon.setBuiltin(icon.getIconType()); + freemindNode.getArrowlinkOrCloudOrEdge().push(freemindIcon); + } + }); + }); + } + + private addEdgeNode(freemainMap: FreeminNode, mindmapTopic: INodeModel): void { + if (mindmapTopic.getBorderColor()) { + const edgeNode: Edge = this.objectFactory.createEdge(); + edgeNode.setColor(this.rgbToHex(mindmapTopic.getBorderColor())); + freemainMap.getArrowlinkOrCloudOrEdge().push(edgeNode); + } + } + + private addFontNode(freemindNode: FreeminNode, mindmapTopic: INodeModel): void { + const fontStyle: string = mindmapTopic.getFontStyle(); + if (fontStyle) { + const font: Font = this.objectFactory.createFont(); + const part: Array = fontStyle.split(';', 6); + const countParts: number = part.length; + let fontNodeNeeded = false; + + if (!fontStyle.endsWith(FreemindConstant.EMPTY_FONT_STYLE)) { + let idx = 0; + + if (idx < countParts && part[idx].length !== 0) { + font.setName(part[idx]); + fontNodeNeeded = true; + } + idx++; + + if (idx < countParts && part[idx].length !== 0) { + const size: string = part[idx]; + if (size) { + const wiseSize: number = parseInt(size, 10); + const freeSize: number = FreemindExporter.wisweToFreeFontSize.get(wiseSize); + + if (freeSize) { + font.setSize(freeSize.toString()); + fontNodeNeeded = true; + } + } + } + idx++; + + if (idx < countParts && part[idx].length !== 0) { + freemindNode.setColor(this.rgbToHex(part[idx])); + } + idx++; + + if (idx < countParts && part[idx].length !== 0) { + font.setBold(String(true)); + fontNodeNeeded = true; + } + idx++; + + if (idx < countParts && part[idx].length !== 0) { + font.setItalic(String(true)); + fontNodeNeeded = true; + } + + if (fontNodeNeeded) { + if (font.getSize() === null) { + font.setSize(FreemindExporter.wisweToFreeFontSize.get(8).toString()); + } + freemindNode.getArrowlinkOrCloudOrEdge().push(font); + } + } + } + } + + private rgbToHex(color: string): string { + let result: string = color; + if (result) { + const isRgb = new RegExp('^rgb\\([0-9]{1,3}, [0-9]{1,3}, [0-9]{1,3}\\)$'); + + if (isRgb.test(result)) { + const rgb: string[] = color.substring(4, color.length - 1).split(','); + const r: string = rgb[0].trim(); + const g: string = rgb[1].trim(); + const b: string = rgb[2].trim(); + + result = `#${r.length === 1 ? `0${r}` : r}${g.length === 1 ? `0${g}` : g}${b.length === 1 ? `0${b}` : b}`; + } + } + return result; + } + + private getVersion(): VersionNumber { + return this.version; + } + + private getVersionNumber(): string { + return this.getVersion().getVersion(); + } +} + +export default FreemindExporter; diff --git a/packages/mindplot/src/components/export/TextExporterFactory.ts b/packages/mindplot/src/components/export/TextExporterFactory.ts index 022da43f..72970b4c 100644 --- a/packages/mindplot/src/components/export/TextExporterFactory.ts +++ b/packages/mindplot/src/components/export/TextExporterFactory.ts @@ -20,6 +20,7 @@ import Exporter from './Exporter'; import MDExporter from './MDExporter'; import TxtExporter from './TxtExporter'; import WiseXMLExporter from './WiseXMLExporter'; +import FreemindExporter from './FreemindExporter'; type textType = 'wxml' | 'txt' | 'mm' | 'csv' | 'md'; @@ -36,6 +37,9 @@ class TextExporterFactory { case 'md': result = new MDExporter(mindmap); break; + case 'mm': + result = new FreemindExporter(mindmap); + break; default: throw new Error(`Unsupported type ${type}`); } diff --git a/packages/mindplot/src/components/export/freemind/Arrowlink.ts b/packages/mindplot/src/components/export/freemind/Arrowlink.ts new file mode 100644 index 00000000..69bf4874 --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/Arrowlink.ts @@ -0,0 +1,86 @@ +export default class Arrowlink { + protected COLOR: string; + + protected DESTINATION: string; + + protected ENDARROW: string; + + protected ENDINCLINATION: string; + + protected ID: string; + + protected STARTARROW: string; + + protected STARTINCLINATION: string; + + getColor(): string { + return this.COLOR; + } + + getDestination(): string { + return this.DESTINATION; + } + + getEndarrow(): string { + return this.ENDARROW; + } + + getEndInclination(): string { + return this.ENDINCLINATION; + } + + getId(): string { + return this.ID; + } + + getStartarrow(): string { + return this.STARTARROW; + } + + getStartinclination(): string { + return this.STARTINCLINATION; + } + + setColor(value: string): void { + this.COLOR = value; + } + + setDestination(value: string): void { + this.DESTINATION = value; + } + + setEndarrow(value: string): void { + this.ENDARROW = value; + } + + setEndinclination(value: string): void { + this.ENDINCLINATION = value; + } + + setId(value: string): void { + this.ID = value; + } + + setStartarrow(value: string): void { + this.STARTARROW = value; + } + + setStartinclination(value: string): void { + this.STARTINCLINATION = value; + } + + toXml(document: Document): HTMLElement { + const arrowlinkElem = document.createElement('arrowlink'); + + arrowlinkElem.setAttribute('DESTINATION', this.DESTINATION); + arrowlinkElem.setAttribute('STARTARROW', this.STARTARROW); + + if (this.COLOR) arrowlinkElem.setAttribute('COLOR', this.COLOR); + if (this.ENDINCLINATION) arrowlinkElem.setAttribute('ENDINCLINATION', this.ENDINCLINATION); + if (this.ENDARROW) arrowlinkElem.setAttribute('ENDARROW', this.ENDARROW); + if (this.ID) arrowlinkElem.setAttribute('ID', this.ID); + if (this.STARTINCLINATION) arrowlinkElem.setAttribute('STARTINCLINATION', this.STARTINCLINATION); + + return arrowlinkElem; + } +} diff --git a/packages/mindplot/src/components/export/freemind/Cloud.ts b/packages/mindplot/src/components/export/freemind/Cloud.ts new file mode 100644 index 00000000..67986fec --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/Cloud.ts @@ -0,0 +1,20 @@ +export default class Cloud { + protected COLOR: string; + + getColor(): string { + return this.COLOR; + } + + setColor(value: string): void { + this.COLOR = value; + } + + toXml(document: Document): HTMLElement { + // Set node attributes + const cloudElem = document.createElement('cloud'); + + cloudElem.setAttribute('COLOR', this.COLOR); + + return cloudElem; + } +} diff --git a/packages/mindplot/src/components/export/freemind/Edge.ts b/packages/mindplot/src/components/export/freemind/Edge.ts new file mode 100644 index 00000000..b3a041f7 --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/Edge.ts @@ -0,0 +1,42 @@ +export default class Edge { + protected COLOR: string; + + protected STYLE: string; + + protected WIDTH: string; + + getColor(): string { + return this.COLOR; + } + + getStyle(): string { + return this.STYLE; + } + + getWidth(): string { + return this.WIDTH; + } + + setColor(value: string): void { + this.COLOR = value; + } + + setStyle(value: string): void { + this.STYLE = value; + } + + setWidth(value: string): void { + this.WIDTH = value; + } + + toXml(document: Document): HTMLElement { + // Set node attributes + const edgeElem = document.createElement('edge'); + + edgeElem.setAttribute('COLOR', this.COLOR); + if (this.STYLE) edgeElem.setAttribute('STYLE', this.STYLE); + if (this.WIDTH) edgeElem.setAttribute('WIDTH', this.WIDTH); + + return edgeElem; + } +} diff --git a/packages/mindplot/src/components/export/freemind/Font.ts b/packages/mindplot/src/components/export/freemind/Font.ts new file mode 100644 index 00000000..6a87077a --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/Font.ts @@ -0,0 +1,53 @@ +export default class Font { + protected BOLD?: string; + + protected ITALIC?: string; + + protected NAME?: string; + + protected SIZE: string; + + getBold(): string { + return this.BOLD; + } + + getItalic(): string { + return this.ITALIC; + } + + getName(): string { + return this.NAME; + } + + getSize(): string { + return this.SIZE; + } + + setBold(value: string): void { + this.BOLD = value; + } + + setItalic(value: string): void { + this.ITALIC = value; + } + + setName(value: string): void { + this.NAME = value; + } + + setSize(value: string): void { + this.SIZE = value; + } + + toXml(document: Document): HTMLElement { + const fontElem = document.createElement('font'); + + fontElem.setAttribute('SIZE', this.SIZE); + + if (this.BOLD) fontElem.setAttribute('BOLD', this.BOLD); + if (this.ITALIC) fontElem.setAttribute('ITALIC', this.ITALIC); + if (this.NAME) fontElem.setAttribute('NAME', this.NAME); + + return fontElem; + } +} diff --git a/packages/mindplot/src/components/export/freemind/FreemindConstant.ts b/packages/mindplot/src/components/export/freemind/FreemindConstant.ts new file mode 100644 index 00000000..b25f3d5d --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/FreemindConstant.ts @@ -0,0 +1,39 @@ +import VersionNumber from './importer/VersionNumber'; + +export default { + LAST_SUPPORTED_FREEMIND_VERSION: '1.0.1', + + SUPPORTED_FREEMIND_VERSION: new VersionNumber('1.0.1'), + + CODE_VERSION: 'tango', + + SECOND_LEVEL_TOPIC_HEIGHT: 25, + + ROOT_LEVEL_TOPIC_HEIGHT: 25, + + CENTRAL_TO_TOPIC_DISTANCE: 200, + + TOPIC_TO_TOPIC_DISTANCE: 90, + + FONT_SIZE_HUGE: 15, + + FONT_SIZE_LARGE: 10, + + FONT_SIZE_NORMAL: 8, + + FONT_SIZE_SMALL: 6, + + NODE_TYPE: 'NODE', + + BOLD: 'bold', + + ITALIC: 'italic', + + EMPTY_FONT_STYLE: ',,,,,', + + EMPTY_NOTE: '', + + POSITION_LEFT: 'left', + + POSITION_RIGHT: 'right', +}; diff --git a/packages/mindplot/src/components/export/freemind/Hook.ts b/packages/mindplot/src/components/export/freemind/Hook.ts new file mode 100644 index 00000000..b9f868f6 --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/Hook.ts @@ -0,0 +1,33 @@ +import Parameters from './Parameters'; + +export default class Hook { + protected PARAMETERS: Parameters; + + protected TEXT: string; + + protected NAME: string; + + getParameters(): Parameters { + return this.PARAMETERS; + } + + getText(): string { + return this.TEXT; + } + + getName(): string { + return this.NAME; + } + + setParameters(value: Parameters): void { + this.PARAMETERS = value; + } + + setText(value: string): void { + this.TEXT = value; + } + + setName(value: string): void { + this.NAME = value; + } +} diff --git a/packages/mindplot/src/components/export/freemind/Icon.ts b/packages/mindplot/src/components/export/freemind/Icon.ts new file mode 100644 index 00000000..c819f960 --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/Icon.ts @@ -0,0 +1,20 @@ +export default class Icon { + protected BUILTIN: string; + + getBuiltin(): string { + return this.BUILTIN; + } + + setBuiltin(value: string): void { + this.BUILTIN = value; + } + + toXml(document: Document): HTMLElement { + // Set node attributes + const iconElem = document.createElement('icon'); + + iconElem.setAttribute('BUILTIN', this.BUILTIN); + + return iconElem; + } +} diff --git a/packages/mindplot/src/components/export/freemind/Map.ts b/packages/mindplot/src/components/export/freemind/Map.ts new file mode 100644 index 00000000..c1e8472c --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/Map.ts @@ -0,0 +1,105 @@ +import { createDocument } from '@wisemapping/core-js'; +import Arrowlink from './Arrowlink'; +import Cloud from './Cloud'; +import Edge from './Edge'; +import Font from './Font'; +import Icon from './Icon'; +import Node, { Choise } from './Node'; + +export default class Map { + protected node: Node; + + protected version: string; + + getNode(): Node { + return this.node; + } + + getVersion(): string { + return this.version; + } + + setNode(value: Node) { + this.node = value; + } + + setVesion(value: string) { + this.version = value; + } + + toXml(): Document { + const document = createDocument(); + + // Set map attributes + const mapElem = document.createElement('map'); + mapElem.setAttribute('version', this.version); + + document.appendChild(mapElem); + + // Create main node + const mainNode: Node = this.node; + mainNode.setCentralNode(true); + const mainNodeElem = mainNode.toXml(document); + mapElem.appendChild(mainNodeElem); + + const childNodes: Array = mainNode.getArrowlinkOrCloudOrEdge(); + childNodes.forEach((childNode: Choise) => { + const node = this.nodeToXml(childNode, mainNodeElem, document); + mainNodeElem.appendChild(node); + }); + + return document; + } + + private nodeToXml(childNode: Choise, parentNode: HTMLElement, document: Document): HTMLElement { + if (childNode instanceof Node) { + childNode.setCentralNode(false); + const childNodeXml = childNode.toXml(document); + parentNode.appendChild(childNodeXml); + + childNode.getArrowlinkOrCloudOrEdge().forEach((node: Choise) => { + const nodeXml = this.nodeToXml(node, childNodeXml, document); + childNodeXml.appendChild(nodeXml); + }); + + return childNodeXml; + } + + if (childNode instanceof Font) { + const childNodeXml = childNode.toXml(document); + parentNode.appendChild(childNodeXml); + + return childNodeXml; + } + + if (childNode instanceof Edge) { + const childNodeXml = childNode.toXml(document); + parentNode.appendChild(childNodeXml); + + return childNodeXml; + } + + if (childNode instanceof Arrowlink) { + const childNodeXml = childNode.toXml(document); + parentNode.appendChild(childNodeXml); + + return childNodeXml; + } + + if (childNode instanceof Cloud) { + const childNodeXml = childNode.toXml(document); + parentNode.appendChild(childNodeXml); + + return childNodeXml; + } + + if (childNode instanceof Icon) { + const childNodeXml = childNode.toXml(document); + parentNode.appendChild(childNodeXml); + + return childNodeXml; + } + + return parentNode; + } +} diff --git a/packages/mindplot/src/components/export/freemind/Node.ts b/packages/mindplot/src/components/export/freemind/Node.ts new file mode 100644 index 00000000..7153519e --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/Node.ts @@ -0,0 +1,215 @@ +import Arrowlink from './Arrowlink'; +import Cloud from './Cloud'; +import Edge from './Edge'; +import Font from './Font'; +import Hook from './Hook'; +import Icon from './Icon'; +import Richcontent from './Richcontent'; + +class Node { + protected arrowlinkOrCloudOrEdge: Array; + + protected BACKGROUND_COLOR: string; + + protected COLOR: string; + + protected FOLDED: string; + + protected ID: string; + + protected LINK: string; + + protected POSITION: string; + + protected STYLE: string; + + protected TEXT: string; + + protected CREATED: string; + + protected MODIFIED: string; + + protected HGAP: string; + + protected VGAP: string; + + protected WCOORDS: string; + + protected WORDER: string; + + protected VSHIFT: string; + + protected ENCRYPTED_CONTENT: string; + + protected centralNode: boolean; + + getArrowlinkOrCloudOrEdge(): Array { + if (!this.arrowlinkOrCloudOrEdge) { + this.arrowlinkOrCloudOrEdge = new Array(); + } + return this.arrowlinkOrCloudOrEdge; + } + + getBackgorundColor(): string { + return this.BACKGROUND_COLOR; + } + + getColor(): string { + return this.COLOR; + } + + getFolded(): string { + return this.FOLDED; + } + + getId(): string { + return this.ID; + } + + getLink(): string { + return this.LINK; + } + + getPosition(): string { + return this.POSITION; + } + + getStyle(): string { + return this.STYLE; + } + + getText(): string { + return this.TEXT; + } + + getCreated(): string { + return this.CREATED; + } + + getModified(): string { + return this.MODIFIED; + } + + getHgap(): string { + return this.HGAP; + } + + getVgap(): string { + return this.VGAP; + } + + getWcoords(): string { + return this.WCOORDS; + } + + getWorder(): string { + return this.WORDER; + } + + getVshift(): string { + return this.VSHIFT; + } + + getEncryptedContent(): string { + return this.ENCRYPTED_CONTENT; + } + + getCentralNode(): boolean { + return this.centralNode; + } + + setArrowlinkOrCloudOrEdge(value: Array): void { + this.arrowlinkOrCloudOrEdge = value; + } + + setBackgorundColor(value: string): void { + this.BACKGROUND_COLOR = value; + } + + setColor(value: string): void { + this.COLOR = value; + } + + setFolded(value: string): void { + this.FOLDED = value; + } + + setId(value: string): void { + this.ID = value; + } + + setLink(value): void { + this.LINK = value; + } + + setPosition(value: string): void { + this.POSITION = value; + } + + setStyle(value: string): void { + this.STYLE = value; + } + + setText(value: string): void { + this.TEXT = value; + } + + setCreated(value: string): void { + this.CREATED = value; + } + + setModified(value: string): void { + this.MODIFIED = value; + } + + setHgap(value: string): void { + this.HGAP = value; + } + + setVgap(value: string): void { + this.VGAP = value; + } + + setWcoords(value: string): void { + this.WCOORDS = value; + } + + setWorder(value: string): void { + this.WORDER = value; + } + + setVshift(value: string): void { + this.VSHIFT = value; + } + + setEncryptedContent(value: string): void { + this.ENCRYPTED_CONTENT = value; + } + + setCentralNode(value: boolean): void { + this.centralNode = value; + } + + toXml(document: Document): HTMLElement { + // Set node attributes + const nodeElem = document.createElement('node'); + + if (this.centralNode) { + nodeElem.setAttribute('ID', this.ID); + nodeElem.setAttribute('TEXT', this.TEXT); + + return nodeElem; + } + + nodeElem.setAttribute('ID', this.ID); + nodeElem.setAttribute('POSITION', this.POSITION); + nodeElem.setAttribute('STYLE', this.STYLE); + nodeElem.setAttribute('TEXT', this.TEXT); + + return nodeElem; + } +} + +export type Choise = Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | Node + +export default Node; diff --git a/packages/mindplot/src/components/export/freemind/ObjectFactory.ts b/packages/mindplot/src/components/export/freemind/ObjectFactory.ts new file mode 100644 index 00000000..71feb370 --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/ObjectFactory.ts @@ -0,0 +1,51 @@ +import Arrowlink from './Arrowlink'; +import Cloud from './Cloud'; +import Edge from './Edge'; +import Font from './Font'; +import Hook from './Hook'; +import Icon from './Icon'; +import Richcontent from './Richcontent'; +import Map from './Map'; +import Node from './Node'; + +export default class ObjectFactory { + public createParameters(): void { + console.log('parameters'); + } + + public crateArrowlink(): Arrowlink { + return new Arrowlink(); + } + + public createCloud(): Cloud { + return new Cloud(); + } + + public createEdge(): Edge { + return new Edge(); + } + + public createFont(): Font { + return new Font(); + } + + public createHook(): Hook { + return new Hook(); + } + + public createIcon(): Icon { + return new Icon(); + } + + public createRichcontent(): Richcontent { + return new Richcontent(); + } + + public createMap(): Map { + return new Map(); + } + + public createNode(): Node { + return new Node(); + } +} diff --git a/packages/mindplot/src/components/export/freemind/Parameters.ts b/packages/mindplot/src/components/export/freemind/Parameters.ts new file mode 100644 index 00000000..aa67c2d2 --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/Parameters.ts @@ -0,0 +1,11 @@ +export default class Parameters { + protected REMINDUSERAT: number; + + getReminduserat(): number { + return this.REMINDUSERAT; + } + + setReminduserat(value: number): void { + this.REMINDUSERAT = value; + } +} diff --git a/packages/mindplot/src/components/export/freemind/Richcontent.ts b/packages/mindplot/src/components/export/freemind/Richcontent.ts new file mode 100644 index 00000000..1c82d76b --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/Richcontent.ts @@ -0,0 +1,30 @@ +export default class Richcontent { + protected html: string; + + protected type: string; + + getHtml(): string { + return this.html; + } + + getType(): string { + return this.type; + } + + setHtml(value: string): void { + this.html = value; + } + + setType(value: string): void { + this.type = value; + } + + toXml(document: Document): HTMLElement { + // Set node attributes + const richcontentElem = document.createElement('richcontent'); + + richcontentElem.setAttribute('TYPE', this.type); + + return richcontentElem; + } +} diff --git a/packages/mindplot/src/components/export/freemind/freemind_0.9.0.xsd b/packages/mindplot/src/components/export/freemind/freemind_0.9.0.xsd new file mode 100755 index 00000000..c42064e3 --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/freemind_0.9.0.xsd @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/mindplot/src/components/export/freemind/generatedClasses.ts b/packages/mindplot/src/components/export/freemind/generatedClasses.ts new file mode 100644 index 00000000..0c9aaa04 --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/generatedClasses.ts @@ -0,0 +1,3 @@ +import { generateTemplateClassesFromXSD } from 'xsd2ts'; + +generateTemplateClassesFromXSD('./freemind_0.9.0'); diff --git a/packages/mindplot/src/components/export/freemind/importer/VersionNumber.ts b/packages/mindplot/src/components/export/freemind/importer/VersionNumber.ts new file mode 100644 index 00000000..0761213a --- /dev/null +++ b/packages/mindplot/src/components/export/freemind/importer/VersionNumber.ts @@ -0,0 +1,11 @@ +export default class VersionNumber { + protected version: string; + + constructor(version: string) { + this.version = version; + } + + public getVersion(): string { + return this.version; + } +} diff --git a/packages/mindplot/test/unit/export/TextExporterTestSuite.test.ts b/packages/mindplot/test/unit/export/TextExporterTestSuite.test.ts index dec9bb3a..23dbed2a 100644 --- a/packages/mindplot/test/unit/export/TextExporterTestSuite.test.ts +++ b/packages/mindplot/test/unit/export/TextExporterTestSuite.test.ts @@ -8,7 +8,8 @@ import { parseXMLFile, setupBlob, exporterAssert } from './Helper'; setupBlob(); -const testNames = fs.readdirSync(path.resolve(__dirname, './input/')) +const testNames = fs + .readdirSync(path.resolve(__dirname, './input/')) .filter((f) => f.endsWith('.wxml')) .map((filename: string) => filename.split('.')[0]); @@ -56,3 +57,18 @@ describe('MD export test execution', () => { await exporterAssert(testName, exporter); }); }); + +describe('MM export test execution', () => { + test.each(testNames)('Exporting %p suite', async (testName: string) => { + // Load mindmap DOM... + const mindmapPath = path.resolve(__dirname, `./input/${testName}.wxml`); + const mapDocument = parseXMLFile(mindmapPath, 'text/xml'); + + // Convert to mindmap... + const serializer = XMLSerializerFactory.createInstanceFromDocument(mapDocument); + const mindmap: Mindmap = serializer.loadFromDom(mapDocument, testName); + + const exporter = TextExporterFactory.create('mm', mindmap); + await exporterAssert(testName, exporter); + }); +}); diff --git a/yarn.lock b/yarn.lock index 36de9480..e6354d7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1017,6 +1017,18 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== + dependencies: + "@cspotcode/source-map-consumer" "0.8.0" + "@cypress/request@^2.88.10", "@cypress/request@^2.88.6": version "2.88.10" resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" @@ -2494,6 +2506,26 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + "@types/aria-query@^4.2.0": version "4.2.2" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" @@ -3282,30 +3314,6 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.1.tgz#0de2875ac31b46b6c5bb1ae0a7d7f0ba5678dffe" integrity sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw== -"@wisemapping/mindplot@^0.4.15": - version "0.4.15" - resolved "https://registry.yarnpkg.com/@wisemapping/mindplot/-/mindplot-0.4.15.tgz#d4a7aa3a96bd5a91ec7f800eb392be820d97510b" - integrity sha512-4buCwA9VezQylHkZ4c7JB+MBqGAOzOnBUZEjeqkg5hZMSt0mobMD3inp+tStJbZtQQbt7p6KJ/04+ewHmGlBag== - dependencies: - "@types/jquery" "^3.5.11" - "@wisemapping/core-js" "^0.4.0" - "@wisemapping/web2d" "^0.4.0" - jest "^27.4.5" - jquery "3.6.0" - lodash "^4.17.21" - -"@wisemapping/mindplot@^0.4.15": - version "0.4.15" - resolved "https://registry.yarnpkg.com/@wisemapping/mindplot/-/mindplot-0.4.15.tgz#d4a7aa3a96bd5a91ec7f800eb392be820d97510b" - integrity sha512-4buCwA9VezQylHkZ4c7JB+MBqGAOzOnBUZEjeqkg5hZMSt0mobMD3inp+tStJbZtQQbt7p6KJ/04+ewHmGlBag== - dependencies: - "@types/jquery" "^3.5.11" - "@wisemapping/core-js" "^0.4.0" - "@wisemapping/web2d" "^0.4.0" - jest "^27.4.5" - jquery "3.6.0" - lodash "^4.17.21" - "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -3374,7 +3382,7 @@ acorn-walk@^7.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn-walk@^8.0.0: +acorn-walk@^8.0.0, acorn-walk@^8.1.1: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== @@ -13554,6 +13562,24 @@ ts-loader@^9.2.6: micromatch "^4.0.0" semver "^7.3.4" +ts-node@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" + integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== + dependencies: + "@cspotcode/source-map-support" "0.7.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + yn "3.1.1" + ts-node@^9.0.0: version "9.1.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d"