diff --git a/packages/mindplot/package.json b/packages/mindplot/package.json index e8833eaf..1c4a43f4 100644 --- a/packages/mindplot/package.json +++ b/packages/mindplot/package.json @@ -26,7 +26,7 @@ "lint": "eslint src --ext js,ts", "playground": "webpack serve --config webpack.playground.js", "cy:run": "cypress run", - "test:unit": "jest ./test/unit/export/*.ts ./test/unit/layout/*.js", + "test:unit": "jest ./test/unit/export/*.ts ./test/unit/import/*.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" }, diff --git a/packages/mindplot/src/components/export/freemind/Map.ts b/packages/mindplot/src/components/export/freemind/Map.ts index a12b74bd..9cd69068 100644 --- a/packages/mindplot/src/components/export/freemind/Map.ts +++ b/packages/mindplot/src/components/export/freemind/Map.ts @@ -1,4 +1,4 @@ -import { createDocument } from '@wisemapping/core-js'; +import { createDocument, $assert } from '@wisemapping/core-js'; import Arrowlink from './Arrowlink'; import Cloud from './Cloud'; import Edge from './Edge'; @@ -7,7 +7,7 @@ import Icon from './Icon'; import Node, { Choise } from './Node'; import Richcontent from './Richcontent'; -export default class Map { +export default class Freemap { protected node: Node; protected version: string; @@ -52,6 +52,115 @@ export default class Map { return document; } + loadFromDom(dom: Document): Freemap { + $assert(dom, 'dom can not be null'); + + const rootElem = dom.documentElement; + + // Is a freemap? + $assert( + rootElem.tagName === 'map', + `This seem not to be a map document. Found tag: ${rootElem.tagName}`, + ); + + // Start the loading process... + const version = rootElem.getAttribute('version') || '1.0.1'; + const freemap: Freemap = new Freemap(); + freemap.setVesion(version); + + const mainTopicElement = rootElem.firstElementChild; + const mainTopic: Node = this.domToNode(mainTopicElement) as Node; + freemap.setNode(mainTopic); + + // Add all the topics nodes... + const childNodes = Array.from(mainTopicElement.childNodes); + const topicsNodes = childNodes + .filter( + (child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'node', + ) + .map((c) => c as Element); + + topicsNodes.forEach((child) => { + const childNode = this.domToNode(child); + mainTopic.setArrowlinkOrCloudOrEdge(childNode); + }); + + return freemap; + } + + private domToNode(nodeElem: Element): Choise { + if (nodeElem.tagName === 'node') { + const node: Node = new Node().loadFromElement(nodeElem); + + if (nodeElem.childNodes.length > 0) { + const childElement = Array.from(nodeElem.childNodes) + .filter( + (child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'node', + ) + .map((c) => c as Element); + + childElement.forEach((child) => { + const childNode = new Node().loadFromElement(child); + node.setArrowlinkOrCloudOrEdge(childNode); + }); + } + + return node; + } + + if (nodeElem.tagName === 'font') { + const font: Font = new Font(); + if (nodeElem.getAttribute('NAME')) font.setName(nodeElem.getAttribute('NAME')); + if (nodeElem.getAttribute('BOLD')) font.setBold(nodeElem.getAttribute('BOLD')); + if (nodeElem.getAttribute('ITALIC')) font.setItalic(nodeElem.getAttribute('ITALIC')); + if (nodeElem.getAttribute('SIZE')) font.setSize(nodeElem.getAttribute('SIZE')); + + return font; + } + + if (nodeElem.tagName === 'edge') { + const edge = new Edge(); + if (nodeElem.getAttribute('COLOR')) edge.setColor(nodeElem.getAttribute('COLOR')); + if (nodeElem.getAttribute('STYLE')) edge.setStyle(nodeElem.getAttribute('STYLE')); + if (nodeElem.getAttribute('WIDTH')) edge.setWidth(nodeElem.getAttribute('WIDTH')); + + return edge; + } + + if (nodeElem.tagName === 'arrowlink') { + const arrowlink = new Arrowlink(); + if (nodeElem.getAttribute('COLOR')) arrowlink.setColor(nodeElem.getAttribute('COLOR')); + if (nodeElem.getAttribute('DESTINATION')) arrowlink.setDestination(nodeElem.getAttribute('DESTINATION')); + if (nodeElem.getAttribute('ENDARROW')) arrowlink.setEndarrow(nodeElem.getAttribute('ENDARROW')); + if (nodeElem.getAttribute('ENDINCLINATION')) arrowlink.setEndinclination(nodeElem.getAttribute('ENDINCLINATION')); + if (nodeElem.getAttribute('ID')) arrowlink.setId(nodeElem.getAttribute('ID')); + if (nodeElem.getAttribute('STARTARROW')) arrowlink.setStartarrow(nodeElem.getAttribute('STARTARROW')); + if (nodeElem.getAttribute('STARTINCLINATION')) arrowlink.setStartinclination(nodeElem.getAttribute('STARTINCLINATION')); + + return arrowlink; + } + + if (nodeElem.tagName === 'cloud') { + const cloud = new Cloud(); + if (nodeElem.getAttribute('COLOR')) cloud.setColor(nodeElem.getAttribute('COLOR')); + } + + if (nodeElem.tagName === 'icon') { + const icon = new Icon(); + if (nodeElem.getAttribute('BUILTIN')) icon.setBuiltin(nodeElem.getAttribute('BUILTIN')); + } + + if (nodeElem.tagName === 'richcontent') { + const richcontent = new Richcontent(); + + if (nodeElem.getAttribute('TYPE')) richcontent.setType(nodeElem.getAttribute('TYPE')); + if (nodeElem.lastElementChild) richcontent.setHtml(String(nodeElem.lastElementChild)); + } + + const nodeDefault = new Node(); + return nodeDefault; + } + private nodeToXml(childNode: Choise, parentNode: HTMLElement, document: Document): HTMLElement { if (childNode instanceof Node) { childNode.setCentralTopic(false); diff --git a/packages/mindplot/src/components/export/freemind/Node.ts b/packages/mindplot/src/components/export/freemind/Node.ts index 3402e431..68e7e295 100644 --- a/packages/mindplot/src/components/export/freemind/Node.ts +++ b/packages/mindplot/src/components/export/freemind/Node.ts @@ -226,6 +226,46 @@ class Node { return nodeElem; } + + loadFromElement(element: Element): Node { + const node = new Node(); + + const nodeId = element.getAttribute('ID'); + const nodePosition = element.getAttribute('POSITION'); + const nodeStyle = element.getAttribute('STYLE'); + const nodeBGColor = element.getAttribute('BACKGROUND_COLOR'); + const nodeColor = element.getAttribute('COLOR'); + const nodeText = element.getAttribute('TEXT'); + const nodeLink = element.getAttribute('LINK'); + const nodeFolded = element.getAttribute('FOLDED'); + const nodeCreated = element.getAttribute('CREATED'); + const nodeModified = element.getAttribute('MODIFIED'); + const nodeHgap = element.getAttribute('HGAP'); + const nodeVgap = element.getAttribute('VGAP'); + const nodeWcoords = element.getAttribute('WCOORDS'); + const nodeWorder = element.getAttribute('WORDER'); + const nodeVshift = element.getAttribute('VSHIFT'); + const nodeEncryptedContent = element.getAttribute('ENCRYPTED_CONTENT'); + + if (nodeId) node.setId(nodeId); + if (nodePosition) node.setPosition(nodePosition); + if (nodeStyle) node.setStyle(nodeStyle); + if (nodeBGColor) node.setBackgorundColor(nodeBGColor); + if (nodeColor) node.setColor(nodeColor); + if (nodeText) node.setText(nodeText); + if (nodeLink) node.setLink(nodeLink); + if (nodeFolded) node.setFolded(nodeFolded); + if (nodeCreated) node.setCreated(nodeCreated); + if (nodeModified) node.setModified(nodeModified); + if (nodeHgap) node.setHgap(nodeHgap); + if (nodeVgap) node.setVgap(nodeVgap); + if (nodeWcoords) node.setWcoords(nodeWcoords); + if (nodeWorder) node.setWorder(nodeWorder); + if (nodeVshift) node.setVshift(nodeVshift); + if (nodeEncryptedContent) node.setEncryptedContent(nodeEncryptedContent); + + return node; + } } export type Choise = Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | Node diff --git a/packages/mindplot/src/components/export/freemind/importer/VersionNumber.ts b/packages/mindplot/src/components/export/freemind/importer/VersionNumber.ts index 0761213a..621a41c1 100644 --- a/packages/mindplot/src/components/export/freemind/importer/VersionNumber.ts +++ b/packages/mindplot/src/components/export/freemind/importer/VersionNumber.ts @@ -8,4 +8,56 @@ export default class VersionNumber { public getVersion(): string { return this.version; } + + public isGreaterThan(versionNumber: VersionNumber): boolean { + return this.compareTo(versionNumber) < 0; + } + + public compareTo(otherObject: VersionNumber): number { + if (this.equals(otherObject)) { + return 0; + } + + const ownTokinizer = this.getTokinizer(); + const otherTokinizer = otherObject.getTokinizer(); + + for (let i = 0; i < ownTokinizer.length; i++) { + let ownNumber: number; + let ohterNumber: number; + + try { + ownNumber = parseInt(ownTokinizer[i], 10); + ohterNumber = parseInt(otherTokinizer[i], 10); + } catch (e) { + return 1; + } + + if (ownNumber > ohterNumber) { + return 1; + } + if (ownNumber < ohterNumber) { + return -1; + } + } + + return -1; + } + + public equals(o: T): boolean { + if (o instanceof VersionNumber) { + return true; + } + + if (!(o instanceof VersionNumber)) { + return false; + } + + const versionNumber: VersionNumber = o as VersionNumber; + + return !(this.version ? this.version !== versionNumber.version : versionNumber.version); + } + + private getTokinizer(): Array { + return this.getVersion().split('.'); + } } diff --git a/packages/mindplot/src/components/import/FreemindIconConverter.ts b/packages/mindplot/src/components/import/FreemindIconConverter.ts new file mode 100644 index 00000000..4ffc154c --- /dev/null +++ b/packages/mindplot/src/components/import/FreemindIconConverter.ts @@ -0,0 +1,10 @@ +import IconModel from '../model/IconModel'; + +export default class FreemindIconConverter { + private static freeIdToIcon: Map = new Map(); + + public static toWiseId(iconId: string): number | null { + const result: IconModel = this.freeIdToIcon.get(iconId); + return result ? result.getId() : null; + } +} diff --git a/packages/mindplot/src/components/import/FreemindImporter.ts b/packages/mindplot/src/components/import/FreemindImporter.ts new file mode 100644 index 00000000..3bc90dc6 --- /dev/null +++ b/packages/mindplot/src/components/import/FreemindImporter.ts @@ -0,0 +1,406 @@ +import Importer from './Importer'; +import Mindmap from '../model/Mindmap'; +import RelationshipModel from '../model/RelationshipModel'; +import NodeModel from '../model/NodeModel'; +import { TopicShape } from '../model/INodeModel'; +import LinkModel from '../model/LinkModel'; +import IconModel from '../model/IconModel'; +import FreemindConstant from '../export/freemind/FreemindConstant'; +import FreemindMap from '../export/freemind/Map'; +import FreemindNode, { Choise } from '../export/freemind/Node'; +import FreemindFont from '../export/freemind/Font'; +import FreemindEdge from '../export/freemind/Edge'; +import FreemindIcon from '../export/freemind/Icon'; +import FreemindHook from '../export/freemind/Hook'; +import FreemindRichcontent from '../export/freemind/Richcontent'; +import FreemindArrowLink from '../export/freemind/Arrowlink'; +import VersionNumber from '../export/freemind/importer/VersionNumber'; +import FreemindIconConverter from './FreemindIconConverter'; +import NoteModel from '../model/NoteModel'; + +export default class FreemindImporter extends Importer { + private mindmap: Mindmap; + + private freemindMap: FreemindMap; + + private nodesmap: Map; + + private relationship: Array; + + private currentId: number; + + constructor(map: FreemindMap) { + super(); + this.freemindMap = map; + } + + import(nameMap: string, description: string): Promise { + this.mindmap = new Mindmap(nameMap); + this.nodesmap = new Map(); + this.relationship = new Array(); + + const version: string = this.freemindMap.getVersion(); + + if (!version || version.startsWith('freeplane')) { + throw new Error('You seems to be be trying to import a Freeplane map. FreePlane is not supported format.'); + } else { + const mapVersion: VersionNumber = new VersionNumber(version); + if (mapVersion.isGreaterThan(FreemindConstant.SUPPORTED_FREEMIND_VERSION)) { + throw new Error(`FreeMind version ${mapVersion.getVersion()} is not supported.`); + } + } + + const freeNode: FreemindNode = this.freemindMap.getNode(); + this.mindmap.setVersion(FreemindConstant.CODE_VERSION); + const wiseTopic = this.mindmap.createNode('CentralTopic', 0); + wiseTopic.setId(this.currentId++); + wiseTopic.setPosition(0, 0); + + this.convertNodeProperties(freeNode, wiseTopic); + + wiseTopic.setShapeType(TopicShape.ROUNDED_RECT); + + this.nodesmap.set(freeNode.getId(), wiseTopic); + + this.convertChildNodes(freeNode, wiseTopic, this.mindmap, 1); + this.addRelationship(this.mindmap); + + this.mindmap.setDescription(description); + + return Promise.resolve(this.mindmap); + } + + private addRelationship(mindmap: Mindmap): void { + const mapRelaitonship: Array = mindmap.getRelationships(); + + mapRelaitonship.forEach((relationship: RelationshipModel) => { + this.fixRelationshipControlPoints(relationship); + + // Fix dest ID + const destId: string = relationship.getDestCtrlPoint(); + const destTopic: NodeModel = this.nodesmap.get(destId); + relationship.setDestCtrlPoint(destTopic.getId()); + + // Fix src ID + const srcId: string = relationship.getSrcCtrlPoint(); + const srcTopic: NodeModel = this.nodesmap.get(srcId); + relationship.setSrcCtrlPoint(srcTopic.getId()); + + mapRelaitonship.push(relationship); + }); + } + + private fixRelationshipControlPoints(relationship: RelationshipModel): void { + const srcTopic: NodeModel = this.nodesmap.get(relationship.getToNode().toString()); + const destNode: NodeModel = this.nodesmap.get(relationship.getFromNode().toString()); + + // Fix x coord + const srcCtrlPoint: string = relationship.getSrcCtrlPoint(); + if (srcCtrlPoint) { + const coords = srcTopic.getPosition(); + if (coords.x < 0) { + const x = coords.x * -1; + relationship.setSrcCtrlPoint(`${x},${coords.y}`); + + // Fix coord + if (srcTopic.getOrder() && srcTopic.getOrder() % 2 !== 0) { + const y = coords.y * -1; + relationship.setSrcCtrlPoint(`${coords.x},${y}`); + } + } + } + + const destCtrlPoint: string = relationship.getDestCtrlPoint(); + if (destCtrlPoint) { + const coords = destNode.getPosition(); + + if (coords.x < 0) { + const x = coords.x * -1; + relationship.setDestCtrlPoint(`${x},${coords.y}`); + } + + if (destNode.getOrder() && destNode.getOrder() % 2 !== 0) { + const y = coords.y * -1; + relationship.setDestCtrlPoint(`${coords.x},${y}`); + } + } + } + + private convertNodeProperties(freeNode: FreemindNode, wiseTopic: NodeModel): void { + const text: string = freeNode.getText(); + wiseTopic.setText(text); + + const bgColor: string = freeNode.getBackgorundColor(); + wiseTopic.setBackgroundColor(bgColor); + + const shape = this.getShapeFromFreeNode(freeNode); + wiseTopic.setShapeType(shape); + + // Check for style... + const fontStyle = this.generateFontStyle(freeNode, null); + if (fontStyle) { + wiseTopic.setFontStyle(fontStyle); + } + + // Is there any link... + const url: string = freeNode.getLink(); + if (url) { + const link: LinkModel = new LinkModel({ url }); + wiseTopic.addFeature(link); + } + + const folded = Boolean(freeNode.getFolded()); + wiseTopic.setChildrenShrunken(folded); + } + + private convertChildNodes(freeParent: FreemindNode, wiseParent: NodeModel, mindmap: Mindmap, depth: number): void { + const freeChilden = freeParent.getArrowlinkOrCloudOrEdge(); + let currentWiseTopic: NodeModel = wiseParent; + let order = 0; + let firstLevelRightOrder = 0; + let firstLevelLeftOrder = 1; + + freeChilden.forEach((child) => { + if (child instanceof FreemindNode) { + const freeChild: FreemindNode = child as FreemindNode; + const wiseChild = mindmap.createNode('MainTopic', this.currentId++); + + this.nodesmap.set(freeChild.getId(), wiseChild); + + let norder: number; + if (depth !== 1) { + norder = order++; + } else if (freeChild.getPosition() && freeChild.getPosition() === FreemindConstant.POSITION_LEFT) { + norder = firstLevelLeftOrder; + firstLevelLeftOrder += 2; + } else { + norder = firstLevelRightOrder; + firstLevelRightOrder += 2; + } + + wiseChild.setOrder(norder); + + // Convert node position... + const childrenCountSameSide = this.getChildrenCountSameSide(freeChilden, freeChild); + const position: {x: number, y: number} = this.convertPosition(wiseParent, freeChild, depth, norder, childrenCountSameSide); + wiseChild.setPosition(position.x, position.y); + + // Convert the rest of the node properties... + this.convertNodeProperties(freeChild, wiseChild); + + this.convertChildNodes(freeChild, wiseChild, mindmap, depth + 1); + + if (wiseChild !== wiseParent) { + wiseParent.append(wiseChild); + } + + currentWiseTopic = wiseChild; + } + + if (child instanceof FreemindFont) { + const font: FreemindFont = child as FreemindFont; + const fontStyle: string = this.generateFontStyle(freeParent, font); + if (fontStyle) { + currentWiseTopic.setFontStyle(fontStyle); + } + } + + if (child instanceof FreemindEdge) { + const edge: FreemindEdge = child as FreemindEdge; + currentWiseTopic.setBackgroundColor(edge.getColor()); + } + + if (child instanceof FreemindIcon) { + const freeIcon: FreemindIcon = child as FreemindIcon; + const iconId: string = freeIcon.getBuiltin(); + const wiseIconId = FreemindIconConverter.toWiseId(iconId); + if (wiseIconId) { + const mindmapIcon: IconModel = new IconModel({ id: wiseIconId }); + currentWiseTopic.addFeature(mindmapIcon); + } + } + + if (child instanceof FreemindHook) { + const hook: FreemindHook = child as FreemindHook; + const mindmapNote: NoteModel = new NoteModel({ text: '' }); + + let textNote: string = hook.getText(); + if (!textNote) { + textNote = FreemindConstant.EMPTY_NOTE; + mindmapNote.setText(textNote); + currentWiseTopic.addFeature(mindmapNote); + } + } + + if (child instanceof FreemindRichcontent) { + const content: FreemindRichcontent = child as FreemindRichcontent; + const type: string = content.getType(); + + if (type === FreemindConstant.NODE_TYPE) { + const text = this.html2text(content); + currentWiseTopic.setText(text); + } else { + let text = this.html2text(content); + const mindmapNote: NoteModel = new NoteModel({ text: '' }); + text = text || FreemindConstant.EMPTY_NOTE; + mindmapNote.setText(text); + currentWiseTopic.addFeature(mindmapNote); + } + } + + if (child instanceof FreemindArrowLink) { + const arrow: FreemindArrowLink = child as FreemindArrowLink; + const relationship: RelationshipModel = new RelationshipModel(0, 0); + const destId: string = arrow.getDestination(); + + relationship.setSrcCtrlPoint(destId); + relationship.setDestCtrlPoint(freeParent.getId()); + const endinclination: string = arrow.getEndInclination(); + if (endinclination) { + const inclination: Array = endinclination.split(';'); + relationship.setDestCtrlPoint(`${inclination[0]},${inclination[1]}`); + } + + const startinclination: string = arrow.getStartinclination(); + if (startinclination) { + const inclination: Array = startinclination.split(';'); + relationship.setSrcCtrlPoint(`${inclination[0]},${inclination[1]}`); + } + + const endarrow: string = arrow.getEndarrow(); + if (endarrow) { + relationship.setEndArrow(endarrow.toLowerCase() !== 'none'); + } + + const startarrow: string = arrow.getStartarrow(); + if (startarrow) { + relationship.setStartArrow(startarrow.toLowerCase() !== 'none'); + } + + relationship.setLineType(3); + this.relationship.push(relationship); + } + }); + } + + private getChildrenCountSameSide(freeChilden: Array, freeChild: FreemindNode): number { + let result = 0; + let childSide: string = freeChild.getPosition(); + + if (!childSide) { + childSide = FreemindConstant.POSITION_RIGHT; + } + + freeChilden.forEach((child) => { + if (child instanceof FreemindNode) { + const node: FreemindNode = child as FreemindNode; + + let side = node.getPosition(); + if (!side) { + side = FreemindConstant.POSITION_RIGHT; + } + if (childSide === side) { + result++; + } + } + }); + + return result; + } + + private getShapeFromFreeNode(node: FreemindNode): string { + let result: string = node.getStyle(); + + if (result === 'bubble') { + result = TopicShape.ROUNDED_RECT; + } else if (node.getBackgorundColor()) { + result = TopicShape.RECTANGLE; + } else { + result = TopicShape.LINE; + } + return result; + } + + private generateFontStyle(node: FreemindNode, font: FreemindFont | undefined): string { + const fontStyle: Array = []; + + // Font family + if (font) { + fontStyle.push(font.getName()); + } + fontStyle.push(';'); + + // Font Size + if (font) { + const fontSize: number = ((!font.getSize() || parseInt(font.getSize(), 10) < 8) ? FreemindConstant.FONT_SIZE_NORMAL : parseInt(font.getSize(), 10)); + let wiseFontSize: number = FreemindConstant.FONT_SIZE_SMALL; + if (fontSize >= 24) { + wiseFontSize = FreemindConstant.FONT_SIZE_HUGE; + } + if (fontSize >= 16) { + wiseFontSize = FreemindConstant.FONT_SIZE_LARGE; + } + if (fontSize >= 8) { + wiseFontSize = FreemindConstant.FONT_SIZE_NORMAL; + } + fontStyle.push(wiseFontSize.toString()); + } + fontStyle.push(';'); + + // Font Color + const color: string = node.getColor(); + if (color && color !== '') { + fontStyle.push(color); + } + fontStyle.push(';'); + + // Font Italic + if (font) { + const hasItalic = Boolean(font.getItalic()); + fontStyle.push(hasItalic ? FreemindConstant.ITALIC : ''); + } + fontStyle.push(';'); + + const result: string = fontStyle.join(''); + return result; + } + + private convertPosition(wiseParent: NodeModel, freeChild: FreemindNode, depth: number, order: number, childrenCount: number): {x: number, y: number} { + let x: number = FreemindConstant.CENTRAL_TO_TOPIC_DISTANCE + ((depth - 1) * FreemindConstant.TOPIC_TO_TOPIC_DISTANCE); + if (depth === 1) { + const side: string = freeChild.getPosition(); + x *= (side && FreemindConstant.POSITION_LEFT === side ? -1 : 1); + } else { + const position = wiseParent.getPosition(); + x *= position.x < 0 ? 1 : -1; + } + + let y: number; + if (depth === 1) { + if (order % 2 === 0) { + const multiplier = ((order + 1) - childrenCount) * 2; + y = multiplier * FreemindConstant.ROOT_LEVEL_TOPIC_HEIGHT; + } else { + const multiplier = (order - childrenCount) * 2; + y = multiplier * FreemindConstant.ROOT_LEVEL_TOPIC_HEIGHT; + } + } else { + const position = wiseParent.getPosition(); + y = position.y - ((childrenCount / 2) * FreemindConstant.SECOND_LEVEL_TOPIC_HEIGHT - (order * FreemindConstant.SECOND_LEVEL_TOPIC_HEIGHT)); + } + + return { + x, + y, + }; + } + + private html2text(content: FreemindRichcontent): string { + const parser = new DOMParser(); + const doc = parser.parseFromString(content.getHtml(), 'text/html'); + doc.querySelector('br').append('\\n'); + doc.querySelector('p').append('\\n'); + doc.querySelector('div').append('\\n'); + return doc.textContent.replace('\\\\n', '\\n').trim(); + } +} diff --git a/packages/mindplot/src/components/import/Importer.ts b/packages/mindplot/src/components/import/Importer.ts new file mode 100644 index 00000000..c4749c97 --- /dev/null +++ b/packages/mindplot/src/components/import/Importer.ts @@ -0,0 +1,5 @@ +import Mindmap from '../model/Mindmap'; + +export default abstract class Importer { + abstract import(nameMap: string, description: string): Promise; +} diff --git a/packages/mindplot/src/components/import/TextImporterFactory.ts b/packages/mindplot/src/components/import/TextImporterFactory.ts new file mode 100644 index 00000000..29c3e935 --- /dev/null +++ b/packages/mindplot/src/components/import/TextImporterFactory.ts @@ -0,0 +1,19 @@ +import FreemindImporter from './FreemindImporter'; +import FreemindMap from '../export/freemind/Map'; +import Importer from './Importer'; + +type textType = 'mm'; +type mapType = FreemindMap + +export default class TextImporterFactory { + static create(type: textType, map: mapType): Importer { + let result: Importer; + switch (type) { + case 'mm': + result = new FreemindImporter(map); + return result; + default: + throw new Error(`Unsupported type ${type}`); + } + } +} diff --git a/packages/mindplot/test/unit/import/TextImporterTestSuite.test.ts b/packages/mindplot/test/unit/import/TextImporterTestSuite.test.ts new file mode 100644 index 00000000..e31baff0 --- /dev/null +++ b/packages/mindplot/test/unit/import/TextImporterTestSuite.test.ts @@ -0,0 +1,36 @@ +import path from 'path'; +import fs from 'fs'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@jest/globals'; +import { parseXMLFile } from '../export/Helper'; +import FreemindMap from '../../../src/components/export/freemind/Map'; +import TextImporterFactory from '../../../src/components/import/TextImporterFactory'; +import XMLSerializerFactory from '../../../src/components/persistence/XMLSerializerFactory'; +import Mindmap from '../../../src/components/model/Mindmap'; + +const testNames = fs + .readdirSync(path.resolve(__dirname, './input/')) + .map((filename: string) => filename.split('.')[0]); + +describe('MMa import test execution', () => { + test.each(testNames)('Importing %p suite', async (testName: string) => { + // load freemap... + const freemapPath = path.resolve(__dirname, `./input/${testName}.mm`); + const mapDocument = parseXMLFile(freemapPath, 'text/xml'); + + const freemap: FreemindMap = new FreemindMap().loadFromDom(mapDocument); + + // Load mindmap DOM.. + const mindmapPath = path.resolve(__dirname, `./expected/${testName}.wxml`); + const mindmapMapDocument = parseXMLFile(mindmapPath, 'text/xml'); + + // Convert to mindmap... + const serializer = XMLSerializerFactory.createInstanceFromDocument(mindmapMapDocument); + const mindmap: Mindmap = serializer.loadFromDom(mapDocument, testName); + + const importer = TextImporterFactory.create('mm', freemap); + const mindmapImport: Mindmap = await importer.import(testName, ''); + + expect(mindmapImport).toStrictEqual(mindmap); + }); +}); diff --git a/packages/mindplot/test/unit/import/expected/bug2.wxml b/packages/mindplot/test/unit/import/expected/bug2.wxml new file mode 100644 index 00000000..713e55e2 --- /dev/null +++ b/packages/mindplot/test/unit/import/expected/bug2.wxml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/mindplot/test/unit/import/input/bug2.mm b/packages/mindplot/test/unit/import/input/bug2.mm new file mode 100644 index 00000000..3baf15c5 --- /dev/null +++ b/packages/mindplot/test/unit/import/input/bug2.mm @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Todos los contenidos de los talleres están relacionados con el currículo de la enseñanza básica.

+

A diferencia de la práctica tradicional, pretendemos ahondar en el conocimiento partiendo de lo que realmente interesa al niño o niña,

+

ayudándole a que encuentre respuesta a las preguntas que él o ella se plantea.

+

+

Por ese motivo, SaberMás proyecta estar al lado de los niños que necesitan una motivación extra para entender la escuela y fluir en ella,

+

y también al lado de aquellos a quienes la curiosidad y las ganas de saber les lleva más allá.

+ + +
+ + + + + + + +
\ No newline at end of file