| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 | ;;; ox-freemind.el --- Freemind Mindmap Back-End for Org Export Engine;; Copyright (C) 2013  Free Software Foundation, Inc.;; Author: Jambunathan K <kjambunathan at gmail dot com>;; Keywords: outlines, hypermedia, calendar, wp;; This file is not part of GNU Emacs.;; This program is free software: you can redistribute it and/or modify;; it under the terms of the GNU General Public License as published by;; the Free Software Foundation, either version 3 of the License, or;; (at your option) any later version.;; This program is distributed in the hope that it will be useful,;; but WITHOUT ANY WARRANTY; without even the implied warranty of;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the;; GNU General Public License for more details.;; You should have received a copy of the GNU General Public License;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.;;; Commentary:;; This library implements a Freemind Mindmap back-end for Org generic;; exporter.;; To test it, run:;;;;   M-x org-freemind-export-to-freemind;;;; in an Org mode buffer.  See ox.el for more details on how this;; exporter works.;;; Code:;;; Dependencies(require 'ox-html);;; Define Back-End(org-export-define-derived-backend 'freemind 'html  :export-block "FREEMIND"  :menu-entry  '(?f "Export to Freemind Mindmap"       ((?f "As Freemind Mindmap file" org-freemind-export-to-freemind)	(?o "As Freemind Mindmap file and open"	    (lambda (a s v b)	      (if a (org-freemind-export-to-freemind t s v b)		(org-open-file (org-freemind-export-to-freemind nil s v b)))))))  :translate-alist '((headline . org-freemind-headline)		     (template . org-freemind-template)		     (inner-template . org-freemind-inner-template)		     (section . org-freemind-section)		     (entity . org-freemind-entity))  :filters-alist '((:filter-options . org-freemind-options-function)		   (:filter-final-output . org-freemind-final-function)));;; User Configuration Variables(defgroup org-export-freemind nil  "Options for exporting Org mode files to Freemind Mindmap."  :tag "Org Export Freemind Mindmap"  :group 'org-export)(defcustom org-freemind-styles  '((default . "<node>\n</node>")    (0 . "<node COLOR=\"#000000\">\n<font NAME=\"SansSerif\" SIZE=\"20\"/>\n</node>")    (1 . "<node COLOR=\"#0033ff\">\n<edge STYLE=\"sharp_bezier\" WIDTH=\"8\"/>\n<font NAME=\"SansSerif\" SIZE=\"18\"/>\n</node>")    (2 . "<node COLOR=\"#00b439\">\n<edge STYLE=\"bezier\" WIDTH=\"thin\"/>\n<font NAME=\"SansSerif\" SIZE=\"16\"/>\n</node>")    (3 . "<node COLOR=\"#990000\" FOLDED=\"true\">\n<font NAME=\"SansSerif\" SIZE=\"14\"/>\n</node>")    (4 . "<node COLOR=\"#111111\">\n</node>"))  "List of Freemind node styles.Each entry is of the form (STYLE-NAME . STYLE-SPEC).  STYLE-NAMEcan be one of an integer (signifying an outline level), a stringor the symbol `default'.  STYLE-SPEC, a string, is a Freemindnode style."  :type '(alist :options (default 0 1 2 3)                :key-type (choice :tag "Style tag"				  (integer :tag "Outline level")				  (const :tag "Default value" default)				  (string :tag "Node style"))                :value-type (string :tag "Style spec"))  :group 'org-export-freemind)(defcustom org-freemind-style-map-function 'org-freemind-style-map--automatic  "Function to map an Org element to it's node style.The mapping function takes two arguments an Org ELEMENT and INFO.ELEMENT can be one of the following types - `org-data',`headline' or `section'.  INFO is a plist holding contextualinformation during export.  The function must return a STYLE-SPECto be applied to ELEMENT.See `org-freemind-style-map--automatic' for a sample stylefunction.  See `org-freemind-styles' for a list of named styles."  :type '(radio	  (function-item org-freemind-style-map--automatic)	  (function-item org-freemind-style-map--default)	  function)  :group 'org-export-freemind)(defcustom org-freemind-section-format 'note  "Specify how outline sections are to be formatted.If `inline', append it to the contents of it's heading node.  If`note', attach it as a note to it's heading node.  If `node',attach it as a separate node to it's heading node.Use `note', if the input Org file contains large sections.  Use`node', if the Org file contains mid-sized sections that need tostand apart.  Otherwise, use `inline'."  :type '(choice	  (const :tag "Append to outline title" inline)	  (const :tag "Attach as a note" note)	  (const :tag "Create a separate node" node))  :group 'org-export-freemind);;;; Debugging(defcustom org-freemind-pretty-output nil  "Enable this to generate pretty Freemind Mindmap."  :type 'boolean  :group 'org-export-freemind);;; Internal Functions;;;; XML Manipulation(defun org-freemind--serialize (parsed-xml &optional contents)  "Convert PARSED-XML in to XML string.PARSED-XML is a parse tree as returned by`libxml-parse-xml-region'.  CONTENTS is an optional string.Ignore CONTENTS, if PARSED-XML is not a sole XML element.Otherwise, append CONTENTS to the contents of top-level elementin PARSED-XML.This is an inverse function of `libxml-parse-xml-region'.For purposes of Freemind export, PARSED-XML is a node stylespecification - \"<node ...>...</node>\" - as a parse tree."  (when contents    (assert (symbolp (car parsed-xml))))  (cond   ((null parsed-xml) "")   ((stringp parsed-xml) parsed-xml)   ((symbolp (car parsed-xml))    (let ((attributes (mapconcat		       (lambda (av)			 (format "%s=\"%s\"" (car av) (cdr av)))		       (cadr parsed-xml) " ")))      (if (or (cddr parsed-xml) contents)	  (format "\n<%s%s>%s\n</%s>"		  (car parsed-xml)		  (if (string= attributes "") "" (concat " " attributes))		  (concat (org-freemind--serialize (cddr parsed-xml))			  contents )		  (car parsed-xml))	(format "\n<%s%s/>"		(car parsed-xml)		(if (string= attributes "") "" (concat " " attributes))))))   (t (mapconcat #'org-freemind--serialize parsed-xml ""))))(defun org-freemind--parse-xml (xml-string)  "Return parse tree for XML-STRING using `libxml-parse-xml-region'.For purposes of Freemind export, XML-STRING is a node stylespecification - \"<node ...>...</node>\" - as a string."  (with-temp-buffer    (insert (or xml-string ""))    (libxml-parse-xml-region (point-min) (point-max))));;;; Style mappers :: Default and Automatic layout(defun org-freemind-style-map--automatic (element info)  "Return a node style corresponding to relative outline level of ELEMENT.ELEMENT can be any of the following types - `org-data',`headline' or `section'.  See `org-freemind-styles' for stylemappings of different outline levels."  (let ((style-name	 (case (org-element-type element)	   (headline	    (org-export-get-relative-level element info))	   (section	    (let ((parent (org-export-get-parent-headline element)))	      (if (not parent) 1		(1+ (org-export-get-relative-level parent info)))))	   (t 0))))    (or (assoc-default style-name org-freemind-styles)	(assoc-default 'default org-freemind-styles)	"<node></node>")))(defun org-freemind-style-map--default (element info)  "Return the default style for all ELEMENTs.ELEMENT can be any of the following types - `org-data',`headline' or `section'.  See `org-freemind-styles' for currentvalue of default style."  (or (assoc-default 'default org-freemind-styles)      "<node></node>"));;;; Helpers :: Retrieve, apply Freemind styles(defun org-freemind--get-node-style (element info)  "Return Freemind node style applicable for HEADLINE.ELEMENT is an Org element of type `org-data', `headline' or`section'.  INFO is a plist holding contextual information."  (unless (fboundp org-freemind-style-map-function)    (setq org-freemind-style-map-function 'org-freemind-style-map--default))  (let ((style (funcall org-freemind-style-map-function element info)))    ;; Sanitize node style.    ;; Loop through the attributes of node element and purge those    ;; attributes that look suspicious.  This is an extra bit of work    ;; that allows one to copy verbatim node styles from an existing    ;; Freemind Mindmap file without messing with the exported data.    (let* ((data (org-freemind--parse-xml style))	   (attributes (cadr data))	   (ignored-attrs '(POSITION FOLDED TEXT CREATED ID				     MODIFIED)))      (let (attr)	(while (setq attr (pop ignored-attrs))	  (setq attributes (assq-delete-all attr attributes))))      (when data (setcar (cdr data) attributes))      (org-freemind--serialize data))))(defun org-freemind--build-stylized-node (style-1 style-2 &optional contents)  "Build a Freemind node with style STYLE-1 + STYLE-2 and add CONTENTS to it.STYLE-1 and STYLE-2 are Freemind node styles as a string.STYLE-1 is the base node style and STYLE-2 is the overridingstyle that takes precedence over STYLE-1.  CONTENTS is a string.Return value is a Freemind node with following properties:  1. The attributes of \"<node ...> </node>\" element is the union     of corresponding attributes of STYLE-1 and STYLE-2.  When     STYLE-1 and STYLE-2 specify values for the same attribute     name, choose the attribute value from STYLE-2.  2. The children of \"<node ...> </node>\" element is the union of     top-level children of STYLE-1 and STYLE-2 with CONTENTS     appended to it.  When STYLE-1 and STYLE-2 share a child     element of same type, the value chosen is that from STYLE-2.For example, merging with following parameters  STYLE-1  =>              <node COLOR=\"#00b439\" STYLE=\"Bubble\">                <edge STYLE=\"bezier\" WIDTH=\"thin\"/>                <font NAME=\"SansSerif\" SIZE=\"16\"/>              </node>  STYLE-2  =>              <node COLOR=\"#990000\" FOLDED=\"true\">                <font NAME=\"SansSerif\" SIZE=\"14\"/>              </node>  CONTENTS =>               <attribute NAME=\"ORGTAG\" VALUE=\"@home\"/>will result in following node:  RETURN   =>               <node STYLE=\"Bubble\" COLOR=\"#990000\" FOLDED=\"true\">                 <edge STYLE=\"bezier\" WIDTH=\"thin\"/>                 <font NAME=\"SansSerif\" SIZE=\"14\"/>                 <attribute NAME=\"ORGTAG\" VALUE=\"@home\"/>               </node>."  (let* ((data1 (org-freemind--parse-xml (or style-1 "")))	 (data2 (org-freemind--parse-xml (or style-2 "")))	 (attr1 (cadr data1))	 (attr2 (cadr data2))	 (merged-attr attr2)	 (children1 (cddr data1))	 (children2 (cddr data2))	 (merged-children children2))    (let (attr)      (while (setq attr (pop attr1))	(unless (assq (car attr) merged-attr)	  (push attr merged-attr))))    (let (child)      (while (setq child (pop children1))	(when (or (stringp child) (not (assq (car child) merged-children)))	  (push child merged-children))))    (let ((merged-data (nconc (list 'node merged-attr) merged-children)))      (org-freemind--serialize merged-data contents))));;;; Helpers :: Node contents(defun org-freemind--richcontent (type contents &optional css-style)  (let* ((type (case type		 (note "NOTE")		 (node "NODE")		 (t "NODE")))	 (contents (org-trim contents)))    (if (string= (org-trim contents) "") ""      (format "\n<richcontent TYPE=\"%s\">%s\n</richcontent>"	      type	      (format "\n<html>\n<head>%s\n</head>\n%s\n</html>"		      (or css-style "")		      (format "<body>\n%s\n</body>" contents))))))(defun org-freemind--build-node-contents (element contents info)  (let* ((title (case (org-element-type element)		  (headline		   (org-element-property :title element))		  (org-data		   (plist-get info :title))		  (t (error "Shouldn't come here."))))	 (element-contents (org-element-contents element))	 (section (assq 'section element-contents))	 (section-contents	  (let ((backend (org-export-create-backend			  :parent (org-export-backend-name				   (plist-get info :back-end))			  :transcoders '((section . (lambda (e c i) c))))))	    (org-export-data-with-backend section backend info)))	 (itemized-contents-p (let ((first-child-headline				     (org-element-map element-contents					 'headline 'identity info t)))				(when first-child-headline				  (org-export-low-level-p first-child-headline							  info))))	 (node-contents (concat section-contents				(when itemized-contents-p				  contents))))    (concat (let ((title (org-export-data title info)))	      (case org-freemind-section-format		(inline		  (org-freemind--richcontent		   'node (concat (format "\n<h2>%s</h2>" title)				 node-contents) ))		(note		 (concat (org-freemind--richcontent			  'node (format "\n<p>%s\n</p>" title))			 (org-freemind--richcontent			  'note node-contents)))		(node		 (concat		  (org-freemind--richcontent		   'node (format "\n<p>%s\n</p>" title))		  (when section		    (org-freemind--build-stylized-node		     (org-freemind--get-node-style section info) nil		     (org-freemind--richcontent 'node node-contents)))))))	    (unless itemized-contents-p	      contents))));;; Template(defun org-freemind-template (contents info)  "Return complete document string after Freemind Mindmap conversion.CONTENTS is the transcoded contents string.  RAW-DATA is theoriginal parsed data.  INFO is a plist holding export options."  (format   "<map version=\"0.9.0\">\n%s\n</map>"   (org-freemind--build-stylized-node    (org-freemind--get-node-style nil info) nil    (let ((org-data (plist-get info :parse-tree)))      (org-freemind--build-node-contents org-data contents info)))))(defun org-freemind-inner-template (contents info)  "Return body of document string after Freemind Mindmap conversion.CONTENTS is the transcoded contents string.  INFO is a plistholding export options."  contents);;;; Tags(defun org-freemind--tags (tags)  (mapconcat (lambda (tag)	       (format "\n<attribute NAME=\"%s\" VALUE=\"%s\"/>" tag ""))	     tags "\n"));;; Transcode Functions;;;; Entity(defun org-freemind-entity (entity contents info)  "Transcode an ENTITY object from Org to Freemind Mindmap.CONTENTS are the definition itself.  INFO is a plist holdingcontextual information."  (org-element-property :utf-8 entity));;;; Headline(defun org-freemind-headline (headline contents info)  "Transcode a HEADLINE element from Org to Freemind Mindmap.CONTENTS holds the contents of the headline.  INFO is a plistholding contextual information."  ;; Empty contents?  (setq contents (or contents ""))  (let* ((numberedp (org-export-numbered-headline-p headline info))	 (level (org-export-get-relative-level headline info))	 (text (org-export-data (org-element-property :title headline) info))	 (todo (and (plist-get info :with-todo-keywords)		    (let ((todo (org-element-property :todo-keyword headline)))		      (and todo (org-export-data todo info)))))	 (todo-type (and todo (org-element-property :todo-type headline)))	 (tags (and (plist-get info :with-tags)		    (org-export-get-tags headline info)))	 (priority (and (plist-get info :with-priority)			(org-element-property :priority headline)))	 (section-number (and (not (org-export-low-level-p headline info))			      (org-export-numbered-headline-p headline info)			      (mapconcat 'number-to-string					 (org-export-get-headline-number					  headline info) ".")))	 ;; Create the headline text.	 (full-text (org-export-data (org-element-property :title headline)				     info))	 ;; Headline order (i.e, first digit of the section number)	 (headline-order (car (org-export-get-headline-number headline info))))    (cond     ;; Case 1: This is a footnote section: ignore it.     ((org-element-property :footnote-section-p headline) nil)     ;; Case 2. This is a deep sub-tree, export it as a list item.     ;;         Delegate the actual export to `html' backend.     ((org-export-low-level-p headline info)      (org-html-headline headline contents info))     ;; Case 3. Standard headline.  Export it as a section.     (t      (let* ((section-number (mapconcat 'number-to-string					(org-export-get-headline-number					 headline info) "-"))	     (ids (remove 'nil			  (list (org-element-property :CUSTOM_ID headline)				(concat "sec-" section-number)				(org-element-property :ID headline))))	     (preferred-id (car ids))	     (extra-ids (cdr ids))	     (left-p (zerop (% headline-order 2))))	(org-freemind--build-stylized-node	 (org-freemind--get-node-style headline info)	 (format "<node ID=\"%s\" POSITION=\"%s\" FOLDED=\"%s\">\n</node>"		 preferred-id		 (if left-p "left" "right")		 (if (= level 1) "true" "false"))	 (concat (org-freemind--build-node-contents headline contents info)		 (org-freemind--tags tags))))))));;;; Section(defun org-freemind-section (section contents info)  "Transcode a SECTION element from Org to Freemind Mindmap.CONTENTS holds the contents of the section.  INFO is a plistholding contextual information."  (let ((parent (org-export-get-parent-headline section)))    (when (and parent (org-export-low-level-p parent info))      contents)));;; Filter Functions(defun org-freemind-final-function (contents backend info)  "Return CONTENTS as pretty XML using `indent-region'."  (if (not org-freemind-pretty-output) contents    (with-temp-buffer      (nxml-mode)      (insert contents)      (indent-region (point-min) (point-max))      (buffer-substring-no-properties (point-min) (point-max)))))(defun org-freemind-options-function (info backend)  "Install script in export options when appropriate.EXP-PLIST is a plist containing export options.  BACKEND is theexport back-end currently used."  ;; Freemind/Freeplane doesn't seem to like named html entities in  ;; richcontent.  For now, turn off smart quote processing so that  ;; entities like "’" & friends are avoided in the exported  ;; output.  (plist-put info :with-smart-quotes nil));;; End-user functions;;;###autoload(defun org-freemind-export-to-freemind  (&optional async subtreep visible-only body-only ext-plist)  "Export current buffer to a Freemind Mindmap file.If narrowing is active in the current buffer, only export itsnarrowed part.If a region is active, export that region.A non-nil optional argument ASYNC means the process should happenasynchronously.  The resulting file should be accessible throughthe `org-export-stack' interface.When optional argument SUBTREEP is non-nil, export the sub-treeat point, extracting information from the headline propertiesfirst.When optional argument VISIBLE-ONLY is non-nil, don't exportcontents of hidden elements.When optional argument BODY-ONLY is non-nil, only write codebetween \"<body>\" and \"</body>\" tags.EXT-PLIST, when provided, is a property list with externalparameters overriding Org default settings, but still inferior tofile-local settings.Return output file's name."  (interactive)  (let* ((extension (concat ".mm" ))	 (file (org-export-output-file-name extension subtreep))	 (org-export-coding-system 'utf-8))    (org-export-to-file 'freemind file      async subtreep visible-only body-only ext-plist)))(provide 'ox-freemind);;; ox-freemind.el ends here
 |