/* * Created on 04.02.2007 * Copyright (C) 2006 Dimitri Polivaev */ package freemind.modes.mindmapmode; import java.io.IOException; import java.io.Writer; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import freemind.extensions.PermanentNodeHook; import freemind.main.HtmlTools; import freemind.main.Resources; import freemind.main.Tools; import freemind.modes.MindIcon; class MindMapHTMLWriter { private Writer fileout; private static String el = System.getProperty("line.separator"); private boolean writeFoldingCode; private boolean basedOnHeadings; private boolean exportIcons; MindMapHTMLWriter(Writer fileout) { this.fileout = fileout; exportIcons = false; writeFoldingCode = false; basedOnHeadings = (getProperty("html_export_folding") .equals("html_export_based_on_headings")); } private static String convertSpecialChar(char c) { String cvt; // try { // // Create the encoder and decoder for ISO-8859-1 // Charset ansi = Charset.forName("windows-1252"); // CharsetDecoder decoder = ansi.newDecoder(); // // Charset utf8 = Charset.forName("UTF-8"); // CharsetEncoder encoder = utf8.newEncoder(); // // // The new ByteBuffer is ready to be read. // ByteBuffer bb = ByteBuffer.allocate(2); // bb.putChar(c); // CharBuffer cb = decoder.decode(bb); // // cvt = cvt + cb.toString(); // } catch (Exception e) { // //cvt = "CHAR ENC FAILED " + e.getMessage(); // cvt = cvt + "&#" + Character.toString(c) + ";"; // } switch ((int) c) { case 0xe4: cvt = "ä"; break; case 0xf6: cvt = "ö"; break; case 0xfc: cvt = "ü"; break; case 0xc4: cvt = "Ä"; break; case 0xd6: cvt = "Ö"; break; case 0xdc: cvt = "Ü"; break; case 0xdf: cvt = "ß"; break; default: cvt = "&#" + Integer.toString((int) c) + ";"; break; } return cvt; } private static String saveHTML_escapeUnicodeAndSpecialCharacters(String text) { int len = text.length(); StringBuffer result = new StringBuffer(len); int intValue; char myChar; boolean previousSpace = false; boolean spaceOccured = false; for (int i = 0; i < len; ++i) { myChar = text.charAt(i); intValue = (int) text.charAt(i); if (!Resources.getInstance().getBoolProperty("wh_nonascii_in_utf8") && intValue >= 128) { result.append(convertSpecialChar(myChar)); } else { spaceOccured = false; switch (myChar) { case '&': result.append("&"); break; case '<': result.append("<"); break; case '>': result.append(">"); break; case ' ': spaceOccured = true; if (previousSpace) { result.append(" "); } else { result.append(" "); } break; case '\n': result.append("\n<br>\n"); break; default: result.append(myChar); } previousSpace = spaceOccured; } } return result.toString(); } void saveHTML(List mindMapNodes) throws IOException { fileout.write("<html>" + el + "<head>" + el); writeStyle(); fileout.write(el + "</head>" + el + "<body>" + el); Iterator iterator = mindMapNodes.iterator(); while (iterator.hasNext()) { MindMapNodeModel node = (MindMapNodeModel) iterator.next(); saveHTML(node, "1", 0, /* isRoot */true, true, /* depth */1); } fileout.write("</body>" + el); fileout.write("</html>" + el); fileout.close(); } void saveHTML(MindMapNodeModel rootNodeOfBranch) throws IOException { // When isRoot is true, rootNodeOfBranch will be exported as folded // regardless his isFolded state in the mindmap. // We do all the HTML saving using just ordinary output. String htmlExportFoldingOption = getProperty("html_export_folding"); writeFoldingCode = (htmlExportFoldingOption .equals("html_export_fold_currently_folded") && rootNodeOfBranch .hasFoldedStrictDescendant()) || htmlExportFoldingOption.equals("html_export_fold_all"); exportIcons = Resources.getInstance().getBoolProperty( "export_icons_in_html"); fileout.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">" + el + "<html>" + el + "<head>" + el); fileout.write("<title>" + saveHTML_escapeUnicodeAndSpecialCharacters(rootNodeOfBranch .getPlainTextContent().replace('\n', ' ')) + "</title>" + el); writeStyle(); fileout.write(el + "</head>" + el + "<body>" + el); if (writeFoldingCode) { writeBodyWithFolding(rootNodeOfBranch); } else { saveHTML(rootNodeOfBranch, "1", 0, /* isRoot */true, true, /* depth */ 1); } fileout.write("</body>" + el); fileout.write("</html>" + el); fileout.close(); } private void writeBodyWithFolding(MindMapNodeModel rootNodeOfBranch) throws IOException { writeJavaScript(); fileout.write("<SPAN class=\"foldspecial\" onclick=\"fold_document()\">All +</SPAN>" + el); fileout.write("<SPAN class=\"foldspecial\" onclick=\"unfold_document()\">All -</SPAN>" + el); // fileout.write("<ul>"); saveHTML(rootNodeOfBranch, "1", 0, /* isRoot */true, true, /* depth */ 1); // fileout.write("</ul>"); fileout.write("<SCRIPT type=\"text/javascript\">" + el); fileout.write("fold_document();" + el); fileout.write("</SCRIPT>" + el); } private void writeJavaScript() throws IOException { fileout.write("" + el + "<script type=\"text/javascript\">" + el + " // Here we implement folding. It works fine with MSIE5.5, MSIE6.0 and" + el + " // Mozilla 0.9.6." + el + "" + el + " if (document.layers) {" + el + " //Netscape 4 specific code" + el + " pre = 'document.';" + el + " post = ''; }" + el + " if (document.getElementById) {" + el + " //Netscape 6 specific code" + el + " pre = 'document.getElementById(\"';" + el + " post = '\").style'; }" + el + " if (document.all) {" + el + " //IE4+ specific code" + el + " pre = 'document.all.';" + el + " post = '.style'; }" + el + "" + el + "function layer_exists(layer) {" + el + " try {" + el + " eval(pre + layer + post);" + el + " return true; }" + el + " catch (error) {" + el + " return false; }}" + el + "" + el + "function show_layer(layer) {" + el + " eval(pre + layer + post).position = 'relative'; " + el + " eval(pre + layer + post).visibility = 'visible'; }" + el + "" + el + "function hide_layer(layer) {" + el + " eval(pre + layer + post).visibility = 'hidden';" + el + " eval(pre + layer + post).position = 'absolute'; }" + el + "" + el + "function hide_folder(folder) {" + el + " hide_folding_layer(folder)" + el + " show_layer('show'+folder);" + el + "" + el + " scrollBy(0,0); // This is a work around to make it work in Browsers (Explorer, Mozilla)" + el + "}" + el + "" + el + "function show_folder(folder) {" + el + " // Precondition: all subfolders are folded" + el + "" + el + " show_layer('hide'+folder);" + el + " hide_layer('show'+folder);" + el + " show_layer('fold'+folder);" + el + "" + el + " scrollBy(0,0); // This is a work around to make it work in Browsers (Explorer, Mozilla)" + el + "" + el + " var i;" + el + " for (i=1; layer_exists('fold'+folder+'_'+i); ++i) {" + el + " show_layer('show'+folder+'_'+i); }" + el + "}" + el + "" + "function show_folder_completely(folder) {" + el + " // Precondition: all subfolders are folded" + el + "" + el + " show_layer('hide'+folder);" + el + " hide_layer('show'+folder);" + el + " show_layer('fold'+folder);" + el + "" + el + " scrollBy(0,0); // This is a work around to make it work in Browsers (Explorer, Mozilla)" + el + "" + el + " var i;" + el + " for (i=1; layer_exists('fold'+folder+'_'+i); ++i) {" + el + " show_folder_completely(folder+'_'+i); }" + el + "}" + el + "" + el + "" + el + "" + el + "function hide_folding_layer(folder) {" + el + " var i;" + el + " for (i=1; layer_exists('fold'+folder+'_'+i); ++i) {" + el + " hide_folding_layer(folder+'_'+i); }" + el + "" + el + " hide_layer('hide'+folder);" + el + " hide_layer('show'+folder);" + el + " hide_layer('fold'+folder);" + el + "" + el + " scrollBy(0,0); // This is a work around to make it work in Browsers (Explorer, Mozilla)" + el + "}" + el + "" + el + "function fold_document() {" + el + " var i;" + el + " var folder = '1';" + el + " for (i=1; layer_exists('fold'+folder+'_'+i); ++i) {" + el + " hide_folder(folder+'_'+i); }" + el + "}" + el + "" + el + "function unfold_document() {" + el + " var i;" + el + " var folder = '1';" + el + " for (i=1; layer_exists('fold'+folder+'_'+i); ++i) {" + el + " show_folder_completely(folder+'_'+i); }" + el + "}" + el + "" + el + "</script>" + el); } private void writeStyle() throws IOException { fileout.write("<style type=\"text/css\">" + el); fileout.write(" li { list-style: none; margin: 0; }" + el); fileout.write(" p { margin: 0; }" + el); if (writeFoldingCode) { fileout.write(" span.foldopened { color: white; font-size: xx-small;" + el + " border-width: 1; font-family: monospace; padding: 0em 0.25em 0em 0.25em; background: #e0e0e0;" + el + " VISIBILITY: visible;" + el + " cursor:pointer; }" + el + "" + el + "" + el + " span.foldclosed { color: #666666; font-size: xx-small;" + el + " border-width: 1; font-family: monospace; padding: 0em 0.25em 0em 0.25em; background: #e0e0e0;" + el + " VISIBILITY: hidden;" + el + " cursor:pointer; }" + el + "" + el + " span.foldspecial { color: #666666; font-size: xx-small; border-style: none solid solid none;" + el + " border-color: #CCCCCC; border-width: 1; font-family: sans-serif; padding: 0em 0.1em 0em 0.1em; background: #e0e0e0;" + el + " cursor:pointer; }" + el); } fileout.write(el + " span.l { color: red; font-weight: bold; }" + el + "" + el + " a.mapnode:link {text-decoration: none; color: black; }" + el + " a.mapnode:visited {text-decoration: none; color: black; }" + el + " a.mapnode:active {text-decoration: none; color: black; }" + el + " a.mapnode:hover {text-decoration: none; color: black; background: #eeeee0; }" + el + "" + el + "</style>" + el + "<!-- ^ Position is not set to relative / absolute here because of Mozilla -->"); } private int saveHTML(MindMapNodeModel model, String parentID, int lastChildNumber, boolean isRoot, boolean treatAsParagraph, int depth) throws IOException { // return lastChildNumber // Not very beautiful solution, but working at least and logical too. boolean createFolding = model.isFolded(); if (getProperty("html_export_folding").equals("html_export_fold_all")) { createFolding = model.hasChildren(); } if (getProperty("html_export_folding").equals("html_export_no_folding") || basedOnHeadings || isRoot) { createFolding = false; } final boolean heading = isHeading(model, depth); if (!treatAsParagraph && !basedOnHeadings) fileout.write("<li>"); else { if (heading) { fileout.write("<h" + depth + ">"); } else if (!hasHtml(model)) { fileout.write("<p>"); } } String localParentID = parentID; if (createFolding) { // lastChildNumber = new Integer lastChildNumber.intValue() + 1; // Change value of an integer lastChildNumber++; localParentID = parentID + "_" + lastChildNumber; writeFoldingButtons(localParentID); } for (Iterator it = model.getActivatedHooks().iterator(); it.hasNext();) { PermanentNodeHook hook = (PermanentNodeHook) it.next(); hook.saveHtml(fileout); } String link = model.getLink(); if (link != null) { if (link.endsWith(".mm")) { link += ".html"; } fileout.write("<a href=\"" + HtmlTools.unicodeToHTMLUnicodeEntity(link, false) + "\" target=\"_blank\"><span class=l>~</span> "); } String fontStyle = fontStyle(model); if (!fontStyle.equals("")) { fileout.write("<span style=\"" + fontStyle + "\">"); } if (Resources.getInstance().getBoolProperty("export_icons_in_html")) { writeIcons(model); } writeModelContent(model); if (fontStyle != "") { fileout.write("</span>"); } fileout.write(el); if (link != null) { fileout.write("</a>" + el); } if (heading) { fileout.write("</h" + depth + ">" + el); } // Are the children to be treated as paragraphs? boolean treatChildrenAsParagraph = false; for (ListIterator e = model.sortedChildrenUnfolded(); e.hasNext();) { if (((MindMapNodeModel) e.next()).toString().length() > 100) { // TODO: // replace // heuristic // constant treatChildrenAsParagraph = true; break; } } // Write the children // Export based on headings if (getProperty("html_export_folding").equals( "html_export_based_on_headings")) { for (ListIterator e = model.sortedChildrenUnfolded(); e.hasNext();) { MindMapNodeModel child = (MindMapNodeModel) e.next(); lastChildNumber = saveHTML(child, parentID, lastChildNumber,/* * isRoot * = */ false, treatChildrenAsParagraph, depth + 1); } return lastChildNumber; } // Export not based on headings if (model.hasChildren()) { if (getProperty("html_export_folding").equals( "html_export_based_on_headings")) { for (ListIterator e = model.sortedChildrenUnfolded(); e.hasNext();) { MindMapNodeModel child = (MindMapNodeModel) e.next(); lastChildNumber = saveHTML(child, parentID, lastChildNumber,/* isRoot= */false, treatChildrenAsParagraph, depth + 1); } } else if (createFolding) { fileout.write("<ul id=\"fold" + localParentID + "\" style=\"POSITION: relative; VISIBILITY: visible;\">"); if (treatChildrenAsParagraph) { fileout.write("<li>"); } int localLastChildNumber = 0; for (ListIterator e = model.sortedChildrenUnfolded(); e.hasNext();) { MindMapNodeModel child = (MindMapNodeModel) e.next(); localLastChildNumber = saveHTML(child, localParentID, localLastChildNumber,/* isRoot= */false, treatChildrenAsParagraph, depth + 1); } } else { fileout.write("<ul>"); if (treatChildrenAsParagraph) { fileout.write("<li>"); } for (ListIterator e = model.sortedChildrenUnfolded(); e.hasNext();) { MindMapNodeModel child = (MindMapNodeModel) e.next(); lastChildNumber = saveHTML(child, parentID, lastChildNumber,/* isRoot= */false, treatChildrenAsParagraph, depth + 1); } } if (treatChildrenAsParagraph) { fileout.write("</li>"); } fileout.write(el); fileout.write("</ul>"); } // End up the node if (!treatAsParagraph) { fileout.write(el + "</li>" + el); } return lastChildNumber; } private String fontStyle(MindMapNodeModel model) throws IOException { String fontStyle = ""; if (model.getColor() != null) { fontStyle += "color: " + Tools.colorToXml(model.getColor()) + ";"; } if (model.getFont() != null && model.getFont().getSize() != 0) { int defaultFontSize = Integer .parseInt(getProperty("defaultfontsize")); int procentSize = (int) (model.getFont().getSize() * 100 / defaultFontSize); if (procentSize != 100) { fontStyle += "font-size: " + procentSize + "%;"; } } if (model.getFont() != null) { String fontFamily = model.getFont().getFamily(); fontStyle += "font-family: " + fontFamily + ", sans-serif; "; } if (model.isItalic()) { fontStyle += "font-style: italic; "; } if (model.isBold()) { fontStyle += "font-weight: bold; "; } // ------------------------ return fontStyle; } private void writeModelContent(MindMapNodeModel model) throws IOException { if (model.toString().matches(" *")) { fileout.write(" "); } else { String output = model.toString(); if (HtmlTools.isHtmlNode(output)) { output = HtmlTools.extractHtmlBody(output); fileout.write(HtmlTools.unicodeToHTMLUnicodeEntity(output, false)); } else { fileout.write(saveHTML_escapeUnicodeAndSpecialCharacters(model .toString())); } } // note output has to be investigated. // if(model.getNoteText() != null){ // // there is a note. give it out: // writeFoldingButtons("note_"+Math.random()); // String output = model.getNoteText(); // output = HtmlTools.extractHtmlBody(output); // fileout.write(HtmlTools.unicodeToHTMLUnicodeEntity(output)); // } } private void writeIcons(MindMapNodeModel model) throws IOException { for (int i = 0; i < model.getIcons().size(); ++i) { fileout.write("<img src=\"" + ((MindIcon) model.getIcons().get(i)).getIconFileName() + "\" alt=\"" + ((MindIcon) model.getIcons().get(i)).getDescription() + "\">"); } } private void writeFoldingButtons(String localParentID) throws IOException { fileout.write("<span id=\"show" + localParentID + "\" class=\"foldclosed\" onClick=\"show_folder('" + localParentID + "')\" style=\"POSITION: absolute\">+</span> " + "<span id=\"hide" + localParentID + "\" class=\"foldopened\" onClick=\"hide_folder('" + localParentID + "')\">-</span>"); fileout.write("\n"); } private boolean isHeading(MindMapNodeModel model, int depth) { return basedOnHeadings && model.hasChildren() && depth <= 6 && !hasHtml(model); } boolean hasHtml(MindMapNodeModel model) { return model.getText().startsWith("<html>"); } private String getProperty(String key) { return Resources.getInstance().getProperty(key); } }