/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.internal.wizards; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xmind.core.IControlPoint; import org.xmind.core.IFileEntry; import org.xmind.core.IHtmlNotesContent; import org.xmind.core.IHyperlinkSpan; import org.xmind.core.IImage; import org.xmind.core.INotes; import org.xmind.core.INotesContent; import org.xmind.core.IParagraph; import org.xmind.core.IRelationship; import org.xmind.core.ISheet; import org.xmind.core.ISpan; import org.xmind.core.ITextSpan; import org.xmind.core.ITopic; import org.xmind.core.IWorkbook; import org.xmind.core.marker.IMarkerRef; import org.xmind.core.style.IStyle; import org.xmind.core.style.IStyleSheet; import org.xmind.core.style.IStyled; import org.xmind.core.util.DOMUtils; import org.xmind.core.util.FileUtils; import org.xmind.core.util.HyperlinkUtils; import org.xmind.core.util.Point; import org.xmind.ui.internal.protocols.FilePathParser; import org.xmind.ui.internal.protocols.FileProtocol; import org.xmind.ui.style.Styles; /** * * @author Karelun huang */ public class FreeMindExporter { private static class EdgeData { String color; String style; String width; public EdgeData(String color, String style, String width) { this.color = color; this.style = style; this.width = width; } @Override public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof EdgeData)) return false; EdgeData ed = (EdgeData) obj; return ed.color.equals(this.color) && ed.style.equals(this.style) && ed.width.equals(this.width); } } private static final String IMAGE_FILE = "images"; //$NON-NLS-1$ private ISheet sheet; private String targetPath; private IProgressMonitor monitor; private Document document; private File imageDir = null; private Map<ITopic, EdgeData> edgeDataMap = null; private Map<String, String> markers = null; public FreeMindExporter(ISheet sheet, String targetPath) { this.sheet = sheet; this.targetPath = targetPath; } public ISheet getSheet() { return sheet; } public String getTargetPath() { return targetPath; } public IProgressMonitor getMonitor() { if (monitor == null) monitor = new NullProgressMonitor(); return monitor; } public void setMonitor(IProgressMonitor monitor) { this.monitor = monitor; } public void build() throws InvocationTargetException, InterruptedException { getMonitor().beginTask(null, 100); try { document = getDocumentBuilder().newDocument(); } catch (ParserConfigurationException e) { throw new InvocationTargetException(e); } checkInterrupter(); try { writeContent(); } catch (Throwable e) { throw new InvocationTargetException(e); } try { OutputStream os = new FileOutputStream(targetPath); // put the document's content to write in the tempPath DOMUtils.save(document, os, true); } catch (Exception e) { throw new InvocationTargetException(e); } } private void writeContent() throws Exception { Element mapEle = DOMUtils.createElement(document, "map"); //$NON-NLS-1$ writeMap(mapEle); if (edgeDataMap != null) edgeDataMap.clear(); if (markers != null) markers.clear(); } private void writeMap(Element mapEle) throws Exception { mapEle.setAttribute("version", "0.8.1"); //$NON-NLS-1$ //$NON-NLS-2$ ITopic rootTopic = getSheet().getRootTopic(); cacheMarkers(); writeTopic(mapEle, rootTopic); } private void writeTopic(Element element, ITopic topic) throws Exception { Element nodeEle = DOMUtils.createElement(element, "node"); //$NON-NLS-1$ IStyle style = getStyle(topic); writeColor(nodeEle, topic, style); writeCreateFolderAndId(nodeEle, topic); writeLink(nodeEle, topic); writeModify(nodeEle); writePosition(nodeEle, topic); writeTopicShapeStyle(nodeEle, style); writeImageOrText(nodeEle, topic); WriteVShift(nodeEle, topic); cacheEdge(topic, style); writeEdge(nodeEle, topic); writeArrowLink(nodeEle, topic); writeFont(nodeEle, style); writeIcon(nodeEle, topic); writeHookNotes(nodeEle, topic); writSubTopics(nodeEle, topic); } private void writeHookNotes(Element nodeEle, ITopic topic) { INotes notes = topic.getNotes(); if (notes == null) return; INotesContent content = notes.getContent(INotes.HTML); if (content != null) { Element hookEle = DOMUtils.createElement(nodeEle, "hook"); //$NON-NLS-1$ hookEle.setAttribute("NAME", //$NON-NLS-1$ "accessories/plugins/NodeNote.properties"); //$NON-NLS-1$ Element notesEle = DOMUtils.createElement(hookEle, "text"); //$NON-NLS-1$ IHtmlNotesContent html = (IHtmlNotesContent) content; List<IParagraph> paragraphs = html.getParagraphs(); StringBuilder paraBuffer = new StringBuilder(); for (IParagraph paragraph : paragraphs) { List<ISpan> spans = paragraph.getSpans(); StringBuilder spanBuffer = new StringBuilder(); for (ISpan span : spans) { writeSpan(spanBuffer, span); } if (paraBuffer.length() > 0) { //paraBuffer.append(" "); //$NON-NLS-1$ paraBuffer.append((char) 10); } paraBuffer.append(spanBuffer.toString()); } notesEle.setTextContent(paraBuffer.toString()); } } private void writeSpan(StringBuilder buffer, ISpan span) { if (span instanceof ITextSpan) { String text = ((ITextSpan) span).getTextContent(); buffer.append(text); } else if (span instanceof IHyperlinkSpan) { for (ISpan subSpan : ((IHyperlinkSpan) span).getSpans()) { writeSpan(buffer, subSpan); } } } private void writeIcon(Element nodeEle, ITopic topic) { Set<IMarkerRef> markers = topic.getMarkerRefs(); if (markers == null) return; Iterator<IMarkerRef> iter = markers.iterator(); while (iter.hasNext()) { IMarkerRef next = iter.next(); String icon = findTransferIcon(next); if (icon != null) { Element iconEle = DOMUtils.createElement(nodeEle, "icon"); //$NON-NLS-1$ iconEle.setAttribute("BUILTIN", icon); //$NON-NLS-1$ } } } private String findTransferIcon(IMarkerRef marker) { String markerId = marker.getMarkerId(); if (markerId == null) return null; if (markerId.startsWith("flag")) //$NON-NLS-1$ markerId = "flag"; //$NON-NLS-1$ else if (markerId.contains("star")) //$NON-NLS-1$ markerId = "star"; //$NON-NLS-1$ String icon = markers.get(markerId); if (icon != null) return icon; return null; } private IStyle getStyle(IStyled styleOwner) { if (styleOwner != null) { String styleId = styleOwner.getStyleId(); if (styleId == null) return null; IWorkbook workbook = getSheet().getOwnedWorkbook(); IStyleSheet styleSheet = workbook.getStyleSheet(); return styleSheet.findStyle(styleId); } return null; } private void cacheEdge(ITopic topic, IStyle style) { if (style == null) return; String lineColor = style.getProperty(Styles.LineColor); String lineClass = style.getProperty(Styles.LineClass); String lineWidth = style.getProperty(Styles.LineWidth); if (lineColor == null && lineClass == null && lineWidth == null) return; String edgeStyle = null; if (lineClass != null) { if (lineClass.endsWith("curve")) //$NON-NLS-1$ edgeStyle = "bezier"; //$NON-NLS-1$ else if (lineClass.endsWith("straight")) //$NON-NLS-1$ edgeStyle = "linear"; //$NON-NLS-1$ } String edgeWidth = null; if ("1pt".equals(lineWidth)) //$NON-NLS-1$ edgeWidth = "thin"; //$NON-NLS-1$ else if ("2pt".equals(lineWidth)) //$NON-NLS-1$ edgeWidth = "1"; //$NON-NLS-1$ else if ("3pt".equals(lineWidth)) //$NON-NLS-1$ edgeWidth = "2"; //$NON-NLS-1$ else if ("4pt".equals(lineWidth)) //$NON-NLS-1$ edgeWidth = "4"; //$NON-NLS-1$ else if ("5pt".equals(lineWidth)) //$NON-NLS-1$ edgeWidth = "8"; //$NON-NLS-1$ if (edgeDataMap == null) edgeDataMap = new HashMap<ITopic, EdgeData>(); EdgeData edgeData = new EdgeData(lineColor, edgeStyle, edgeWidth); edgeDataMap.put(topic, edgeData); } private void writeEdge(Element nodeEle, ITopic topic) { if (edgeDataMap == null) return; ITopic parent = topic.getParent(); if (parent == null) return; EdgeData edgeData = edgeDataMap.get(parent); if (edgeData == null) return; Element edgeEle = DOMUtils.createElement(nodeEle, "edge"); //$NON-NLS-1$ String edgeColor = edgeData.color; if (edgeColor != null) edgeEle.setAttribute("COLOR", edgeColor); //$NON-NLS-1$ String edgeStyle = edgeData.style; if (edgeStyle != null) edgeEle.setAttribute("STYLE", edgeStyle); //$NON-NLS-1$ String edgeWidth = edgeData.width; if (edgeWidth != null) edgeEle.setAttribute("WIDTH", edgeWidth); //$NON-NLS-1$ } private void writeColor(Element nodeEle, ITopic topic, IStyle style) { if (style == null) return; String backgroundColor = style.getProperty(Styles.FillColor); if (backgroundColor != null) nodeEle.setAttribute("BACKGROUND_COLOR", backgroundColor); //$NON-NLS-1$ String color = style.getProperty(Styles.TextColor); if (color != null) nodeEle.setAttribute("COLOR", color); //$NON-NLS-1$ } private void writeArrowLink(Element nodeEle, ITopic topic) { List<IRelationship> relationships = findRelationship(topic); if (relationships == null) return; for (IRelationship relationship : relationships) { Element arrowlinkEle = DOMUtils.createElement(nodeEle, "arrowlink"); //$NON-NLS-1$ IStyle style = getRelationStyle(relationship); writeArrowLinkColor(arrowlinkEle, style); boolean canWriteCP = canWriteControlpoint(relationship); writeEndArrow(arrowlinkEle, relationship, style, canWriteCP); String arrowLineId = relationship.getId(); arrowlinkEle.setAttribute("ID", arrowLineId); //$NON-NLS-1$ writeStartArrow(arrowlinkEle, relationship, style, canWriteCP); } } private boolean canWriteControlpoint(IRelationship relationship) { String end1Id = relationship.getEnd1Id(); String end2Id = relationship.getEnd2Id(); IWorkbook workbook = getSheet().getOwnedWorkbook(); ITopic topic1 = workbook.findTopic(end1Id); ITopic topic2 = workbook.findTopic(end2Id); if (topic1.isAttached() && topic2.isAttached()) return true; return false; } private void writeStartArrow(Element arrowlinkEle, IRelationship relationship, IStyle style, boolean canWritePoint) { String arrowStart = null; String startArrow = null; if (style != null) arrowStart = style.getProperty(Styles.ArrowBeginClass); if (arrowStart == null || arrowStart.endsWith("none")) //$NON-NLS-1$ startArrow = "None"; //$NON-NLS-1$ else startArrow = "Default"; //$NON-NLS-1$ arrowlinkEle.setAttribute("STARTARROW", startArrow); //$NON-NLS-1$ if (!canWritePoint) return; IControlPoint controlPoint = relationship.getControlPoint(0); Point p = controlPoint.getPosition(); if (p != null) { String endInc = String.valueOf(p.x) + ";" + String.valueOf(p.y); //$NON-NLS-1$ arrowlinkEle.setAttribute("STARTINCLINATION", endInc); //$NON-NLS-1$ } } private void writeEndArrow(Element arrowlinkEle, IRelationship relationship, IStyle style, boolean canWritePoint) { String endArrowId = relationship.getEnd2Id(); if (endArrowId != null) arrowlinkEle.setAttribute("DESTINATION", endArrowId); //$NON-NLS-1$ String arrowEnd = null; String endArrow = null; if (style != null) arrowEnd = style.getProperty(Styles.ArrowEndClass); if (arrowEnd != null && arrowEnd.endsWith("none")) //$NON-NLS-1$ endArrow = "None"; //$NON-NLS-1$ else endArrow = "Default"; //$NON-NLS-1$ arrowlinkEle.setAttribute("ENDARROW", endArrow); //$NON-NLS-1$ if (!canWritePoint) return; IControlPoint controlPoint = relationship.getControlPoint(1); Point p = controlPoint.getPosition(); if (p != null) { String endInc = String.valueOf(p.x) + ";" + String.valueOf(p.y); //$NON-NLS-1$ arrowlinkEle.setAttribute("ENDINCLINATION", endInc); //$NON-NLS-1$ } } private void writeArrowLinkColor(Element arrowlinkEle, IStyle style) { if (style == null) return; String lineColor = style.getProperty(Styles.LineColor); if (lineColor == null) return; arrowlinkEle.setAttribute("COLOR", lineColor); //$NON-NLS-1$ } private IStyle getRelationStyle(IRelationship relationship) { String styleId = relationship.getStyleId(); IWorkbook workbook = getSheet().getOwnedWorkbook(); IStyleSheet styleSheet = workbook.getStyleSheet(); return styleSheet.findStyle(styleId); } private void WriteVShift(Element nodeEle, ITopic topic) { if (topic.hasPosition() && topic.isAttached()) { Point position = topic.getPosition(); String value = String.valueOf(position.y); nodeEle.setAttribute("VSHIFT", value); //$NON-NLS-1$ } } private void writeImageOrText(Element nodeEle, ITopic topic) throws Exception { IImage image = topic.getImage(); String source = image.getSource(); if (source != null) { File imageDir = getImageDir(); String entryPath = HyperlinkUtils.toAttachmentPath(source); IFileEntry fileEntry = getSheet().getOwnedWorkbook().getManifest() .getFileEntry(entryPath); if (fileEntry != null) { String path = fileEntry.getPath(); int lastIndex = path.lastIndexOf('/'); String fileName = path.substring(lastIndex + 1); InputStream is = fileEntry.getInputStream(); if (is != null) { FileOutputStream os = new FileOutputStream( new File(imageDir, fileName)); FileUtils.transfer(is, os, true); String sourcePath = "images" + "/" + fileName; //$NON-NLS-1$ //$NON-NLS-2$ // " ASCII is 34 String value = "<html><img src=" + (char) 34 + sourcePath //$NON-NLS-1$ + (char) 34 + ">"; //$NON-NLS-1$ nodeEle.setAttribute("TEXT", value); //$NON-NLS-1$ return; } } } String text = topic.getTitleText(); nodeEle.setAttribute("TEXT", text); //$NON-NLS-1$ } private File getImageDir() { if (imageDir == null) { String imageSource = new File(targetPath).getParent(); imageDir = FileUtils .ensureDirectory(new File(imageSource, IMAGE_FILE)); } return imageDir; } private void writSubTopics(Element topicEle, ITopic topic) throws Exception { List<ITopic> children = topic.getAllChildren(); if (children != null) { for (ITopic subTopic : children) writeTopic(topicEle, subTopic); } } private void writeFont(Element nodeEle, IStyle style) { if (style == null) return; String bold = style.getProperty(Styles.FontWeight); String italic = style.getProperty(Styles.FontStyle); String fontSize = style.getProperty(Styles.FontSize); if (bold == null && italic == null && fontSize == null) return; Element fontEle = DOMUtils.createElement(nodeEle, "font"); //$NON-NLS-1$ if (bold != null) { String isBold = "bold".equals(bold) ? "true" : null; //$NON-NLS-1$ //$NON-NLS-2$ if (isBold != null) fontEle.setAttribute("BOLD", isBold); //$NON-NLS-1$ } if (italic != null) { String isItalic = "italic".equals(italic) ? "true" : null; //$NON-NLS-1$ //$NON-NLS-2$ if (isItalic != null) fontEle.setAttribute("ITALIC", isItalic); //$NON-NLS-1$ } fontEle.setAttribute("NAME", "SansSerif"); //$NON-NLS-1$ //$NON-NLS-2$ if (fontSize != null) { int index = fontSize.indexOf("pt"); //$NON-NLS-1$ if (index >= 0) { String size = fontSize.substring(0, index); fontEle.setAttribute("SIZE", size); //$NON-NLS-1$ } else { fontEle.setAttribute("SIZE", fontSize); //$NON-NLS-1$ } } else fontEle.setAttribute("SIZE", "12"); //$NON-NLS-1$ //$NON-NLS-2$ } private void writePosition(Element nodeEle, ITopic topic) { ITopic parent = topic.getParent(); if (parent != null && parent.isRoot()) { List<ITopic> topics = parent.getAllChildren(); int index = topics.indexOf(topic); String value = index <= topics.size() / 2 ? "right" : "left"; //$NON-NLS-1$ //$NON-NLS-2$ nodeEle.setAttribute("POSITION", value); //$NON-NLS-1$ } } private void writeTopicShapeStyle(Element nodeEle, IStyle style) { if (style == null) return; String shapeStyle = null; String shape = style.getProperty(Styles.ShapeClass); if (shape == null) { shapeStyle = "bubble"; //$NON-NLS-1$ return; } if (shape.endsWith("noBorder") || shape.endsWith("undeline")) { //$NON-NLS-1$ //$NON-NLS-2$ shapeStyle = "fork"; //$NON-NLS-1$ } else shapeStyle = "bubble"; //$NON-NLS-1$ nodeEle.setAttribute("STYLE", shapeStyle); //$NON-NLS-1$ } private void writeModify(Element nodeEle) { String value = String.valueOf(System.currentTimeMillis()); nodeEle.setAttribute("MODIFIED", value); //$NON-NLS-1$ } private void writeLink(Element nodeEle, ITopic topic) throws Exception { String hyperlink = topic.getHyperlink(); if (hyperlink == null) return; String link = toLink(topic); if (link != null) { nodeEle.setAttribute("LINK", link); //$NON-NLS-1$ } } private String toLink(ITopic topic) throws Exception { String hyperlink = topic.getHyperlink(); if (hyperlink == null) return null; if (hyperlink.startsWith("file:")) { //$NON-NLS-1$ String path = FilePathParser.toPath(hyperlink); return FileProtocol.getAbsolutePath(topic, path); // if (FilePathParser.isPathRelative(path)) { // IWorkbook workbook = topic.getOwnedWorkbook(); // String base = workbook.getFile(); // if (base != null) { // base = new File(base).getParent(); // if (base != null) { // return FilePathParser.toAbsolutePath(base, path); // } // } // return FilePathParser // .toAbsolutePath(System.getProperty("user.home"), path); //$NON-NLS-1$ // } // return path; } else if (HyperlinkUtils.isInternalURL(hyperlink)) { return HyperlinkUtils.toElementID(hyperlink); } else if (HyperlinkUtils.isAttachmentURL(hyperlink)) { File imageDir = getImageDir(); String entryPath = HyperlinkUtils.toAttachmentPath(hyperlink); IFileEntry fileEntry = topic.getOwnedWorkbook().getManifest() .getFileEntry(entryPath); if (fileEntry == null) return null; String path = fileEntry.getPath(); int lastIndex = path.lastIndexOf('/'); String fileName = path.substring(lastIndex + 1); InputStream is = fileEntry.getInputStream(); if (is != null) { try { FileOutputStream os = new FileOutputStream( new File(imageDir, fileName)); try { FileUtils.transfer(is, os, false); return IMAGE_FILE + "/" + fileName; //$NON-NLS-1$ } finally { os.close(); } } finally { is.close(); } } } else if (HyperlinkUtils.isLinkToWeb(hyperlink) && !hyperlink.contains("http://") //$NON-NLS-1$ && !hyperlink.contains("https://")) //$NON-NLS-1$ return "http://" + hyperlink; //$NON-NLS-1$ return hyperlink; } private void writeCreateFolderAndId(Element nodeEle, ITopic topic) { long times = System.currentTimeMillis(); String value = String.valueOf(times); nodeEle.setAttribute("CREATED", value); //$NON-NLS-1$ if (topic.isFolded()) nodeEle.setAttribute("FOLDER", "true"); //$NON-NLS-1$ //$NON-NLS-2$ String id = topic.getId(); nodeEle.setAttribute("ID", id); //$NON-NLS-1$ } private void cacheMarkers() { if (markers == null) markers = new HashMap<String, String>(); markers.put("priority-1", "full-1"); //$NON-NLS-1$ //$NON-NLS-2$ markers.put("priority-2", "full-2"); //$NON-NLS-1$ //$NON-NLS-2$ markers.put("priority-3", "full-3"); //$NON-NLS-1$//$NON-NLS-2$ markers.put("priority-4", "full-4"); //$NON-NLS-1$ //$NON-NLS-2$ markers.put("priority-5", "full-5"); //$NON-NLS-1$//$NON-NLS-2$ markers.put("priority-6", "full-6"); //$NON-NLS-1$ //$NON-NLS-2$ markers.put("smiley-smile", "ksmiletris"); //$NON-NLS-1$ //$NON-NLS-2$ markers.put("flag", "flag"); //$NON-NLS-1$//$NON-NLS-2$ markers.put("star", "bookmark"); //$NON-NLS-1$ //$NON-NLS-2$ markers.put("other-email", "Mail"); //$NON-NLS-1$ //$NON-NLS-2$ markers.put("other-phone", "kaddressbook"); //$NON-NLS-1$//$NON-NLS-2$ markers.put("other-question", "help"); //$NON-NLS-1$ //$NON-NLS-2$ markers.put("other-lightbulb", "idea"); //$NON-NLS-1$ //$NON-NLS-2$ markers.put("other-unlock", "password"); //$NON-NLS-1$//$NON-NLS-2$ markers.put("other-yes", "button_ok"); //$NON-NLS-1$ //$NON-NLS-2$ markers.put("other-no", "button_cancel"); //$NON-NLS-1$//$NON-NLS-2$ markers.put("other-bomb", "clanbomber"); //$NON-NLS-1$ //$NON-NLS-2$ } private List<IRelationship> findRelationship(ITopic topic) { Set<IRelationship> relationships = getSheet().getRelationships(); if (relationships == null) return null; List<IRelationship> result = null; Iterator<IRelationship> iter = relationships.iterator(); while (iter.hasNext()) { IRelationship next = iter.next(); if (next != null) { String end1Id = next.getEnd1Id(); if (topic.getId().equals(end1Id)) { if (result == null) result = new ArrayList<IRelationship>(); result.add(next); } } } return result; } private void checkInterrupter() throws InterruptedException { if (getMonitor().isCanceled()) throw new InterruptedException(); } private static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException { return DOMUtils.getDefaultDocumentBuilder(); } }