User:Jiangxin/Patch load mm file with mmx file

From FreeMind
Jump to navigationJump to search

Why this patch?

All my .mm files are version controled using CVS/SVN. What FreeMind has scratched my personal itch:

  • Some attributes of FreeMind's .mm file are not suitable for version control.
  • e.g., 'FOLDED' attribute saved in .mm file, makes documents changed frequently and unnecessarily.

How .mmx is saved.

How the .mmx file is generated? Please see this link:

Overview

When load a mindmap file(*.mm), will check whether a .*.mmx file exist at the same directory. It it exist, a xslt will help to join the two files into one big XML.

When I write the patch at 2005, I even don't know the basic knowledge of optimization of a XSLT.

  • Warning: this patch is immature, and it can cause trouble if your FreeMind .mm file is as large as 200KB!
  • Java expert/FreeMind Funs, can you help me to make it usable. Thank you.

Last week when I feel it's time to upgrade to freemind 0.9, I overview and rewrite the xslt and the patch.

-- Jiangxin 02:26, 15 Mar 2007 (PDT)

the XSLT

My implementation is using a XSLT file to join the .mmx file with .mm file in runtime. The XSLT is below:

file : freemind/modes/mindmapmode/freemind_join_mm_mmx.xslt

low performance version

...
		<xsl:copy>
			<xsl:choose>
				<xsl:when test="$mmx_nodes//node[@ID=current()/@ID]">
					<xsl:for-each select="@*">
						<xsl:choose>
							<xsl:when test="local-name(.) = 'FOLDED'">
							</xsl:when>
							<xsl:otherwise>
								<xsl:copy-of select="." />
							</xsl:otherwise>
						</xsl:choose>
					</xsl:for-each>
					<xsl:copy-of select="$mmx_nodes//node[@ID=current()/@ID]/@*" />
				</xsl:when>
				<xsl:otherwise>
					<xsl:copy-of select="@*" />
				</xsl:otherwise>
			</xsl:choose>
			<xsl:apply-templates />
		</xsl:copy>
...

optimized version using key

<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Usage:
   xsltproc -stringparam mmx_file mindmap.mmx <this_xslt> mindmap.mm
-->
   <xsl:output method="xml" version="1.0" encoding="utf-8"
       indent="no" />

   <xsl:param name="mmx_file" />
   <xsl:variable name="indexfile" select="document($mmx_file)" />

   <xsl:key name="node-by-id" match="node" use="@ID"/>

   <xsl:template match="map">
       <map>
           <xsl:copy-of select="@*" />
           <xsl:apply-templates />
       </map>
   </xsl:template>

   <xsl:template match="node">
       <xsl:variable name="id" select="@ID" />
       <xsl:copy>
           <xsl:copy-of select="@*" />
           <xsl:for-each select="$indexfile">
               <xsl:copy-of select="key('node-by-id', $id)/@*" />
           </xsl:for-each>
           <xsl:apply-templates />
       </xsl:copy>
   </xsl:template>

   <xsl:template match="*">
     <xsl:copy-of select="."/>
   </xsl:template>

</xsl:stylesheet>

test the xslt

  • xslt
$ xsltproc --stringparam mmx_file subject-forum.mmx freemind_join_mm_mmx.xslt subject-forum.mm > jx.mm
  • saxon
$ java -cp /usr/share/java/saxon.jar com.icl.saxon.StyleSheet test.mm freemind_join_mm_mmx.xslt  mmx_file=test.mmx
  • xalan
$ java -classpath /usr/share/java/xalan2.jar org.apache.xalan.xslt.Process -IN test.mm  -XSL freemind_join_mm_mmx.xslt -PARAM mmx_file test.mmx

Using saxon xslt engine

The built-in xslt engine does not work with this xslt. I choose saxon xslt engine.

System.setProperty("javax.xml.transform.TransformerFactory", 
    "com.icl.saxon.TransformerFactoryImpl");

Patch

Index: freemind/main/Tools.java
===================================================================
--- a/freemind/freemind/main/Tools.java 
+++ b/freemind/freemind/main/Tools.java 
 import javax.xml.transform.stream.StreamResult;
 import javax.xml.transform.stream.StreamSource;
 
+//OSSXP.COM: classes for .mm and .mmx join.
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.dom.DOMSource;
+import org.w3c.dom.Document;
+
 public class Tools {
 
     //public static final Set executableExtensions = new HashSet ({ "exe",
@@ -845,7 +851,8 @@
 	    } catch(Exception ex) {
 	        freemind.main.Resources.getInstance().logException(ex);
 	        // exception: we take the file itself:
-	        return getActualReader(file);
+	        // OSSXP.COM
+	        return getActualReader(file, frame);
 	    } finally {
 	        if(inputStream!= null) {
 	            inputStream.close();
@@ -857,10 +864,112 @@
 	    return new StringReader(writer.getBuffer().toString());
 	}
 
-	/** Creates a default reader that just reads the given file.
-	 * @throws FileNotFoundException
+	/*
+	 * OSSXP.COM: hacked FreeMind saved two seperate files, .mm and .mmx file.
+	 * Join them in runtime using XSLT TransformerFactory.
+	 * TODO: Improvement needed. the joining stage may very slow, so disable it.
 	 */
-	public static Reader getActualReader(File file) throws FileNotFoundException {
+	public static Reader getActualReader(File file, FreeMindMain frame) throws IOException {
+	    // load .mmx file...
+	    String ext = Tools.getExtension(file.getName());
+	    String mmxFileName = "";
+
+	    // OSSXP.COM: can disable join .mm with .mmx here.
+	    // if(true) return getActualReaderXml(file);
+	    
+	    if(!ext.equals("mm")) 
+	    {
+	    	mmxFileName = "." + file.getName()+".mmx";
+	    }
+	    else 
+	    {
+	    	mmxFileName = "." + Tools.removeExtension(file.getName()) + ".mmx";
+	    }
+	    File mmxfile = new File(file.getParent(), mmxFileName);
+	    
+	    if (!mmxfile.exists())
+	    {
+	    	return getActualReaderXml(file);
+	    }
+
+	    URL updaterUrl = null;
+	    InputStream inputStream = null;
+	    Source xsltSource = null;
+	    StringWriter buffwriter = null;
+	    Result result = null;
+	    TransformerFactory tf = null;
+	    Transformer transformer = null;
+	    String mmxFileFullName = mmxfile.toURI().toString();
+	    try {
+	        // try to convert map with xslt:
+	    	updaterUrl = frame.getResource(
+	                "freemind/modes/mindmapmode/freemind_join_mm_mmx.xslt");
+	        if (updaterUrl == null) {
+	            throw new IllegalArgumentException(
+	                    "freemind_join_mm_mmx.xslt not found.");
+	        }
+	        inputStream = updaterUrl.openStream();
+	        xsltSource = new StreamSource(inputStream);
+	        // get output:
+	        buffwriter = new StringWriter();
+	        result = new StreamResult(buffwriter);
+	        /* OSSXP.COM: create an instance of TransformerFactory.
+	         * the default xslt engine (com.sun.org.apache.xalan.internal.xsltc.trax...) 
+	         * may not support 'key()' in freemind_join_mm_mmx.xslt. 
+	         * Use saxon implement. */
+	        System.setProperty("javax.xml.transform.TransformerFactory", 
+	        		"com.icl.saxon.TransformerFactoryImpl");
+	        tf = TransformerFactory.newInstance();
+	        transformer = tf.newTransformer(xsltSource);
+	        transformer.setParameter("mmx_file", mmxFileFullName);
+	        transformer.transform(new StreamSource(file), result);
+
+	    } catch (Exception ex) {
+	        ex.printStackTrace();
+	        // exception: we take the file itself:
+	        return getActualReaderXml(file);
+	    } finally {
+	        if (inputStream != null) {
+	            inputStream.close();
+	        }
+	        if (buffwriter != null) {
+	            buffwriter.close();
+	        }
+	        inputStream = null;
+	        xsltSource = null;
+	        updaterUrl = null;
+	        result = null;
+	        transformer = null;
+	        tf = null;
+	    }
+	    return new StringReader(buffwriter.getBuffer().toString());
+	}
+
+	/* 
+	 * OSSXP.COM: In this hacked version, .mm file is a true XML file.
+	 * load XML file using DOM.
+	 */
+	private static Reader getActualReaderXml(File file) throws IOException {
+	    try
+	    {
+	        TransformerFactory tf = TransformerFactory.newInstance();
+	        Transformer transformer = tf.newTransformer();
+	        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
+	        DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
+
+	        Document doc = domBuilder.parse(file);
+	        Source src = new DOMSource(doc);
+	        StringWriter buffwriter = new StringWriter();
+	        StreamResult result = new StreamResult(buffwriter);
+	        transformer.transform(src, result);
+
+	        return new StringReader(buffwriter.toString());
+	    }
+	    catch(Exception exp)
+	    {
+	    	exp.printStackTrace();
+	    }
+
 	    return new BufferedReader(new FileReader(file));
 	}
 
Index: freemind/modes/mindmapmode/MindMapMapModel.java
===================================================================
--- a/freemind/freemind/modes/mindmapmode/MindMapMapModel.java 
+++ b/freemind/freemind/modes/mindmapmode/MindMapMapModel.java 
@@ -440,7 +440,7 @@
             }
             if (mapStart.equals(EXPECTED_START_STRINGS[i])) {
                 // actual version:
-                reader = Tools.getActualReader(file);
+                reader = Tools.getActualReader(file, getFrame());
                 break;
             }
         }
Index: build.xml
===================================================================
--- a/freemind/build.xml 
+++ b/freemind/build.xml 
@@ -343,6 +343,7 @@
 				<include name="Resources*"/>
 				<include name="mindmap_menus.xml"/>
 				<include name="**/freemind_version_updater.xslt"/>
+				<include name="**/freemind_join_mm_mmx.xslt"/>
 			</fileset>
 		</jar>
 	</target>