Exporter Wisemaping to Freemind

This commit is contained in:
Ezequiel-Vega 2022-02-11 18:33:22 -03:00
parent 7d7bb9c8b0
commit 4943623ab8
20 changed files with 1246 additions and 27 deletions

View File

@ -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",

View File

@ -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<number, FreeminNode> = null;
private version: VersionNumber = FreemindConstant.SUPPORTED_FREEMIND_VERSION;
private objectFactory: ObjectFactory;
private static wisweToFreeFontSize: Map<number, number> = new Map<number, number>();
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<string> {
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<RelationshipModel> = 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<Choise> = 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<INodeModel> = 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 = '<html><body>';
text.split('\n').forEach((line: string) => {
html += `<p>${line.trim()}</p>`;
});
html += '</body></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<INodeModel> = 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<string> = 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;

View File

@ -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}`);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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',
};

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Choise> = 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;
}
}

View File

@ -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<Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | this>;
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<Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | Node> {
if (!this.arrowlinkOrCloudOrEdge) {
this.arrowlinkOrCloudOrEdge = new Array<Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | this>();
}
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<Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | this>): 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;

View File

@ -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();
}
}

View File

@ -0,0 +1,11 @@
export default class Parameters {
protected REMINDUSERAT: number;
getReminduserat(): number {
return this.REMINDUSERAT;
}
setReminduserat(value: number): void {
this.REMINDUSERAT = value;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name='Parameters'>
<xs:complexType>
<!--Is the time management plugin.-->
<xs:attribute name='REMINDUSERAT' type='xs:integer' use='optional'/>
</xs:complexType>
</xs:element>
<!--Used for node notes.-->
<xs:element name='text' type="xs:string"/>
<xs:element name='arrowlink'>
<xs:complexType>
<xs:attribute name='COLOR' type='xs:string' use='optional'/>
<xs:attribute name='DESTINATION' type='xs:string' use='required'/>
<xs:attribute name='ENDARROW' type='xs:string' use='optional'/>
<xs:attribute name='ENDINCLINATION' type='xs:string' use='optional'/>
<xs:attribute name='ID' type='xs:string' use='optional'/>
<xs:attribute name='STARTARROW' type='xs:string' use='optional'/>
<xs:attribute name='STARTINCLINATION' type='xs:string' use='optional'/>
</xs:complexType>
</xs:element>
<xs:element name='cloud'>
<xs:complexType>
<xs:attribute name='COLOR' type='xs:string' use='optional'/>
</xs:complexType>
</xs:element>
<xs:element name='edge'>
<xs:complexType>
<xs:attribute name='COLOR' type='xs:string' use='optional'/>
<xs:attribute name='STYLE' type='xs:string' use='optional'/>
<xs:attribute name='WIDTH' type='xs:string' use='optional'/>
</xs:complexType>
</xs:element>
<xs:element name='font'>
<xs:complexType>
<xs:attribute name='BOLD' use='optional'>
<xs:simpleType>
<xs:restriction base='xs:string'>
<xs:enumeration value='true'/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name='ITALIC' use='optional'>
<xs:simpleType>
<xs:restriction base='xs:string'>
<xs:enumeration value='true'/>
<xs:enumeration value='false'/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name='NAME' type='xs:string' use='required'/>
<xs:attribute name='SIZE' use='required' type='xs:integer'/>
</xs:complexType>
</xs:element>
<xs:element name='hook'>
<xs:complexType>
<xs:sequence>
<xs:element ref='Parameters' minOccurs='0' maxOccurs='1'/>
<xs:element ref='text' minOccurs='0' maxOccurs='1'/>
</xs:sequence>
<xs:attribute name='NAME' type='xs:string' use='required'/>
</xs:complexType>
</xs:element>
<xs:element name='icon'>
<xs:complexType>
<xs:attribute name='BUILTIN' type='xs:string' use='required'/>
</xs:complexType>
</xs:element>
<xs:element name='richcontent'>
<xs:complexType>
<!-- And contains XHTML as unique child:-->
<xs:sequence>
<xs:element name='html' minOccurs='1' maxOccurs='1' type="xs:anyType"/>
</xs:sequence>
<!--Currently, only NODE or NOTE is allowed.-->
<xs:attribute name='TYPE' use='required'>
<xs:simpleType>
<xs:restriction base='xs:string'>
<xs:enumeration value='NODE'/>
<xs:enumeration value='NOTE'/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name='map'>
<xs:complexType>
<xs:sequence>
<xs:element ref='node'/>
</xs:sequence>
<xs:attribute name='version' type='xs:string' use='required'/>
</xs:complexType>
</xs:element>
<xs:element name='node'>
<xs:complexType>
<xs:choice minOccurs='0' maxOccurs='unbounded'>
<xs:element ref='arrowlink'/>
<xs:element ref='cloud'/>
<xs:element ref='edge'/>
<xs:element ref='font'/>
<xs:element ref='hook'/>
<xs:element ref='icon'/>
<xs:element ref='node'/>
<!-- For nodes with extended formatting content or for notes to nodes. -->
<xs:element ref='richcontent'/>
</xs:choice>
<xs:attribute name='BACKGROUND_COLOR' type='xs:string' use='optional'/>
<xs:attribute name='COLOR' type='xs:string' use='optional'/>
<xs:attribute name='FOLDED' use='optional'>
<xs:simpleType>
<xs:restriction base='xs:string'>
<xs:enumeration value='true'/>
<xs:enumeration value='false'/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name='ID' type='xs:ID' use='optional'/>
<xs:attribute name='LINK' type='xs:string' use='optional'/>
<xs:attribute name='POSITION' use='optional'>
<xs:simpleType>
<xs:restriction base='xs:string'>
<xs:enumeration value='left'/>
<xs:enumeration value='right'/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name='STYLE' type='xs:string' use='optional'/>
<xs:attribute name='TEXT' type='xs:string' use='required'/>
<xs:attribute name='CREATED' type='xs:integer' use='optional'/>
<xs:attribute name='MODIFIED' type='xs:integer' use='optional'/>
<xs:attribute name='HGAP' type='xs:integer' use='optional'/>
<xs:attribute name='VGAP' type='xs:integer' use='optional'/>
<xs:attribute name='WCOORDS' type='xs:string' use='optional'/>
<xs:attribute name='WORDER' type='xs:integer' use='optional'/>
<xs:attribute name='VSHIFT' type='xs:integer' use='optional'/>
<xs:attribute name='ENCRYPTED_CONTENT' type='xs:string' use='optional'/>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@ -0,0 +1,3 @@
import { generateTemplateClassesFromXSD } from 'xsd2ts';
generateTemplateClassesFromXSD('./freemind_0.9.0');

View File

@ -0,0 +1,11 @@
export default class VersionNumber {
protected version: string;
constructor(version: string) {
this.version = version;
}
public getVersion(): string {
return this.version;
}
}

View File

@ -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);
});
});

View File

@ -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"