| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223 | ;;; org-freemind.el --- Export Org files to freemind;; Copyright (C) 2009-2012 Free Software Foundation, Inc.;; Author: Lennart Borgman (lennart O borgman A gmail O com);; Keywords: outlines, hypermedia, calendar, wp;; Homepage: http://orgmode.org;;;; This file is part of GNU Emacs.;;;; GNU Emacs 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.;; GNU Emacs 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/>.;; --------------------------------------------------------------------;; Features that might be required by this library:;;;; `backquote', `bytecomp', `cl', `easymenu', `font-lock',;; `noutline', `org', `org-compat', `org-faces', `org-footnote',;; `org-list', `org-macs', `org-src', `outline', `syntax',;; `time-date', `xml'.;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Commentary:;;;; This file tries to implement some functions useful for;; transformation between org-mode and FreeMind files.;;;; Here are the commands you can use:;;;;    M-x `org-freemind-from-org-mode';;    M-x `org-freemind-from-org-mode-node';;    M-x `org-freemind-from-org-sparse-tree';;;;    M-x `org-freemind-to-org-mode';;;;    M-x `org-freemind-show';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Change log:;;;; 2009-02-15: Added check for next level=current+1;; 2009-02-21: Fixed bug in `org-freemind-to-org-mode'.;; 2009-10-25: Added support for `org-odd-levels-only'.;;             Added y/n question before showing in FreeMind.;; 2009-11-04: Added support for #+BEGIN_HTML.;;;;; Code:(require 'xml)(require 'org);(require 'rx)(require 'org-exp)(eval-when-compile (require 'cl))(defgroup org-freemind nil  "Customization group for org-freemind export/import."  :group 'org);; Fix-me: I am not sure these are useful:;;;; (defcustom org-freemind-main-fgcolor "black";;   "Color of main node's text.";;   :type 'color;;   :group 'org-freemind);; (defcustom org-freemind-main-color "black";;   "Background color of main node.";;   :type 'color;;   :group 'org-freemind);; (defcustom org-freemind-child-fgcolor "black";;   "Color of child nodes' text.";;   :type 'color;;   :group 'org-freemind);; (defcustom org-freemind-child-color "black";;   "Background color of child nodes.";;   :type 'color;;   :group 'org-freemind)(defvar org-freemind-node-style nil "Internal use.")(defcustom org-freemind-node-styles nil  "Styles to apply to node.NOT READY YET."  :type '(repeat          (list :tag "Node styles for file"                (regexp :tag "File name")                (repeat                 (list :tag "Node"                       (regexp :tag "Node name regexp")                       (set :tag "Node properties"                            (list :format "%v" (const :format "" node-style)                                  (choice :tag "Style"                                          :value bubble                                          (const bubble)                                          (const fork)))                            (list :format "%v" (const :format "" color)                                  (color :tag "Color" :value "red"))                            (list :format "%v" (const :format "" background-color)                                  (color :tag "Background color" :value "yellow"))                            (list :format "%v" (const :format "" edge-color)                                  (color :tag "Edge color" :value "green"))                            (list :format "%v" (const :format "" edge-style)                                  (choice :tag "Edge style" :value bezier                                          (const :tag "Linear" linear)                                          (const :tag "Bezier" bezier)                                          (const :tag "Sharp Linear" sharp-linear)                                          (const :tag "Sharp Bezier" sharp-bezier)))                            (list :format "%v" (const :format "" edge-width)                                  (choice :tag "Edge width" :value thin                                          (const :tag "Parent" parent)                                          (const :tag "Thin" thin)                                          (const 1)                                          (const 2)                                          (const 4)                                          (const 8)))                            (list :format "%v" (const :format "" italic)                                  (const :tag "Italic font" t))                            (list :format "%v" (const :format "" bold)                                  (const :tag "Bold font" t))                            (list :format "%v" (const :format "" font-name)                                  (string :tag "Font name" :value "SansSerif"))                            (list :format "%v" (const :format "" font-size)                                  (integer :tag "Font size" :value 12)))))))  :group 'org-freemind);;;###autoload(defun org-export-as-freemind (&optional hidden ext-plist				   to-buffer body-only pub-dir)  "Export the current buffer as a Freemind file.If there is an active region, export only the region.  HIDDEN isobsolete and does nothing.  EXT-PLIST is a property list withexternal parameters overriding org-mode's default settings, butstill inferior to file-local settings.  When TO-BUFFER isnon-nil, create a buffer with that name and export to thatbuffer.  If TO-BUFFER is the symbol `string', don't leave anybuffer behind but just return the resulting HTML as a string.When BODY-ONLY is set, don't produce the file header and footer,simply return the content of the document (all top levelsections).  When PUB-DIR is set, use this as the publishingdirectory.See `org-freemind-from-org-mode' for more information."  (interactive "P")  (let* ((opt-plist (org-combine-plists (org-default-export-plist)					ext-plist					(org-infile-export-plist)))	 (region-p (org-region-active-p))	 (rbeg (and region-p (region-beginning)))	 (rend (and region-p (region-end)))	 (subtree-p	  (if (plist-get opt-plist :ignore-subtree-p)	      nil	    (when region-p	      (save-excursion		(goto-char rbeg)		(and (org-at-heading-p)		     (>= (org-end-of-subtree t t) rend))))))	 (opt-plist (setq org-export-opt-plist			  (if subtree-p			      (org-export-add-subtree-options opt-plist rbeg)			    opt-plist)))	 (bfname (buffer-file-name (or (buffer-base-buffer) (current-buffer))))	 (filename (concat (file-name-as-directory			    (or pub-dir				(org-export-directory :ascii opt-plist)))			   (file-name-sans-extension			    (or (and subtree-p				     (org-entry-get (region-beginning)						    "EXPORT_FILE_NAME" t))				(file-name-nondirectory bfname)))			   ".mm")))    (when (file-exists-p filename)      (delete-file filename))    (cond     (subtree-p      (org-freemind-from-org-mode-node (line-number-at-pos rbeg)				       filename))     (t (org-freemind-from-org-mode bfname filename)))));;;###autoload(defun org-freemind-show (mm-file)  "Show file MM-FILE in Freemind."  (interactive   (list    (save-match-data      (let ((name (read-file-name "FreeMind file: "                                  nil nil nil                                  (if (buffer-file-name)                                      (let* ((name-ext (file-name-nondirectory (buffer-file-name)))                                             (name (file-name-sans-extension name-ext))                                             (ext (file-name-extension name-ext)))                                        (cond                                         ((string= "mm" ext)                                          name-ext)                                         ((string= "org" ext)                                          (let ((name-mm (concat name ".mm")))                                            (if (file-exists-p name-mm)                                                name-mm                                              (message "Not exported to Freemind format yet")                                              "")))                                         (t                                          "")))                                    "")                                  ;; Fix-me: Is this an Emacs bug?                                  ;; This predicate function is never                                  ;; called.                                  (lambda (fn)                                    (string-match "^mm$" (file-name-extension fn))))))        (setq name (expand-file-name name))        name))))  (org-open-file mm-file))(defconst org-freemind-org-nfix "--org-mode: ");;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Format converters(defun org-freemind-escape-str-from-org (org-str)  "Do some html-escaping of ORG-STR and return the result.The characters \"&<> will be escaped."  (let ((chars (append org-str nil))        (fm-str ""))    (dolist (cc chars)      (setq fm-str            (concat fm-str                    (if (< cc 160)                        (cond                         ((= cc ?\") """)                         ((= cc ?\&) "&")                         ((= cc ?\<) "<")                         ((= cc ?\>) ">")                         (t (char-to-string cc)))                      ;; Formatting as &#number; is maybe needed                      ;; according to a bug report from kazuo                      ;; fujimoto, but I have now instead added a xml                      ;; processing instruction saying that the mm                      ;; file is utf-8:                      ;;                      ;; (format "&#x%x;" (- cc ;; ?\x800))		      (format "&#x%x;" (encode-char cc 'ucs))                      ))))    fm-str));;(org-freemind-unescape-str-to-org "mA≌B<C<=");;(org-freemind-unescape-str-to-org "<<")(defun org-freemind-unescape-str-to-org (fm-str) "Do some html-unescaping of FM-STR and return the result.This is the opposite of `org-freemind-escape-str-from-org' but itwill also unescape &#nn;." (let ((org-str fm-str))   (setq org-str (replace-regexp-in-string """ "\"" org-str))   (setq org-str (replace-regexp-in-string "&" "&" org-str))   (setq org-str (replace-regexp-in-string "<" "<" org-str))   (setq org-str (replace-regexp-in-string ">" ">" org-str))   (setq org-str (replace-regexp-in-string                  "&#x\\([a-f0-9]\\{2,4\\}\\);"                  (lambda (m)                    (char-to-string                     (+ (string-to-number (match-string 1 m) 16)                        0 ;?\x800 ;; What is this for? Encoding?                        )))                  org-str))));; (let* ((str1 "a quote: \", an amp: &, lt: <; over 256: öåäÖÅÄ");;        (str2 (org-freemind-escape-str-from-org str1));;        (str3 (org-freemind-unescape-str-to-org str2)));;     (unless (string= str1 str3);;       (error "Error str3=%s" str3)))(defun org-freemind-convert-links-helper (matched)  "Helper for `org-freemind-convert-links-from-org'.MATCHED is the link just matched."  (let* ((link (match-string 1 matched))         (text (match-string 2 matched))         (ext (file-name-extension link))         (col-pos (org-string-match-p ":" link))         (is-img (and (image-type-from-file-name link)                      (let ((url-type (substring link 0 col-pos)))                        (member url-type '("file" "http" "https")))))        )    (if is-img        ;; Fix-me: I can't find a way to get the border to "shrink        ;; wrap" around the image using <div>.        ;;        ;; (concat "<div style=\"border: solid 1px #ddd; width:auto;\">"        ;;         "<img src=\"" link "\" alt=\"" text "\" />"        ;;         "<br />"        ;;         "<i>" text "</i>"        ;;         "</div>")        (concat "<table border=\"0\" style=\"border: solid 1px #ddd;\"><tr><td>"                "<img src=\"" link "\" alt=\"" text "\" />"                "<br />"                "<i>" text "</i>"                "</td></tr></table>")      (concat "<a href=\"" link "\">" text "</a>"))))(defun org-freemind-convert-links-from-org (org-str)  "Convert org links in ORG-STR to freemind links and return the result."  (let ((fm-str (replace-regexp-in-string                 ;;(rx (not (any "[\""))                 ;;    (submatch                 ;;     "http"                 ;;     (opt ?\s)                 ;;     "://"                 ;;     (1+                 ;;      (any "-%.?@a-zA-Z0-9()_/:~=&#"))))		 "[^\"[]\\(http ?://[--:#%&()=?-Z_a-z~]+\\)"                 "[[\\1][\\1]]"                 org-str                 nil ;; fixedcase                 nil ;; literal                 1   ;; subexp                 )))    (replace-regexp-in-string     ;;(rx "[["     ;;	 (submatch (*? nonl))     ;; "]["     ;; (submatch (*? nonl))     ;; "]]")     "\\[\\[\\(.*?\\)]\\[\\(.*?\\)]]"     ;;"<a href=\"\\1\">\\2</a>"     'org-freemind-convert-links-helper     fm-str)));;(org-freemind-convert-links-to-org "<a href=\"http://www.somewhere/\">link-text</a>")(defun org-freemind-convert-links-to-org (fm-str)  "Convert freemind links in FM-STR to org links and return the result."  (let ((org-str (replace-regexp-in-string                  ;;(rx "<a"                  ;;    space                  ;;    (0+                  ;;     (0+ (not (any ">")))                  ;;     space)                  ;;    "href=\""                  ;;    (submatch (0+ (not (any "\""))))                  ;;    "\""                  ;;    (0+ (not (any ">")))                  ;;     ">"                  ;;     (submatch (0+ (not (any "<"))))                  ;;     "</a>")		  "<a[[:space:]]\\(?:[^>]*[[:space:]]\\)*href=\"\\([^\"]*\\)\"[^>]*>\\([^<]*\\)</a>"                  "[[\\1][\\2]]"                  fm-str)))    org-str));; Fix-me:;;(defun org-freemind-convert-drawers-from-org (text);;  );;   (let* ((str1 "[[http://www.somewhere/][link-text]");;          (str2 (org-freemind-convert-links-from-org str1));;        (str3 (org-freemind-convert-links-to-org str2)));;     (unless (string= str1 str3);;     (error "Error str3=%s" str3)));;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Org => FreeMind(defvar org-freemind-bol-helper-base-indent nil)(defun org-freemind-bol-helper (matched)  "Helper for `org-freemind-convert-text-p'.MATCHED is the link just matched."  (let ((res "")        (bi org-freemind-bol-helper-base-indent))    (dolist (cc (append matched nil))      (if (= 32 cc)          ;;(setq res (concat res " "))          ;; We need to use the numerical version. Otherwise Freemind          ;; ver 0.9.0 RC9 can not export to html/javascript.          (progn            (if (< 0 bi)                (setq bi (1- bi))              (setq res (concat res " "))))        (setq res (concat res (char-to-string cc)))))    res));; (setq x (replace-regexp-in-string "\n +" 'org-freemind-bol-nbsp-helper "\n  "))(defun org-freemind-convert-text-p (text)  "Convert TEXT to html with <p> paragraphs."  ;; (string-match-p "[^ ]" "  a")  (setq org-freemind-bol-helper-base-indent (org-string-match-p "[^ ]" text))  (setq text (org-freemind-escape-str-from-org text))  (setq text (replace-regexp-in-string "\\([[:space:]]\\)\\(/\\)\\([^/]+\\)\\(/\\)\\([[:space:]]\\)" "\\1<i>\\3</i>\\5" text))  (setq text (replace-regexp-in-string "\\([[:space:]]\\)\\(\*\\)\\([^*]+\\)\\(\*\\)\\([[:space:]]\\)" "\\1<b>\\3</b>\\5" text))  (setq text (concat "<p>" text))  (setq text (replace-regexp-in-string "\n[[:blank:]]*\n" "</p><p>" text))  (setq text (replace-regexp-in-string "\\(?:<p>\\|\n\\) +" 'org-freemind-bol-helper text))  (setq text (replace-regexp-in-string "\n" "<br />" text))  (setq text (concat text "</p>"))  (org-freemind-convert-links-from-org text))(defcustom org-freemind-node-css-style  "p { margin-top: 3px; margin-bottom: 3px; }"  "CSS style for Freemind nodes."  ;; Fix-me: I do not understand this. It worked to export from Freemind  ;; with this setting now, but not before??? Was this perhaps a java  ;; bug or is it a windows xp bug (some resource gets exhausted if you  ;; use sticky keys which I do).  :group 'org-freemind)(defun org-freemind-org-text-to-freemind-subnode/note (node-name start end drawers-regexp)  "Convert text part of org node to freemind subnode or note.Convert the text part of the org node named NODE-NAME. The textis in the current buffer between START and END. Drawers matchingDRAWERS-REGEXP are converted to freemind notes."  ;; fix-me: doc  (let ((text (buffer-substring-no-properties start end))        (node-res "")        (note-res ""))    (save-match-data      ;;(setq text (org-freemind-escape-str-from-org text))      ;; First see if there is something that should be moved to the      ;; note part:      (let (drawers)        (while (string-match drawers-regexp text)          (setq drawers (cons (match-string 0 text) drawers))          (setq text                (concat (substring text 0 (match-beginning 0))                        (substring text (match-end 0))))          )        (when drawers          (dolist (drawer drawers)            (let ((lines (split-string drawer "\n")))              (dolist (line lines)                (setq note-res (concat                                note-res                                org-freemind-org-nfix line "<br />\n")))              ))))      (when (> (length note-res) 0)        (setq note-res (concat                        "<richcontent TYPE=\"NOTE\"><html>\n"                        "<head>\n"                        "</head>\n"                        "<body>\n"                        note-res                        "</body>\n"                        "</html>\n"                        "</richcontent>\n"))        )      ;; There is always an LF char:      (when (> (length text) 1)        (setq node-res (concat                        "<node style=\"bubble\" background_color=\"#eeee00\">\n"                        "<richcontent TYPE=\"NODE\"><html>\n"                        "<head>\n"                        (if (= 0 (length org-freemind-node-css-style))                            ""                          (concat                        "<style type=\"text/css\">\n"                        "<!--\n"                           org-freemind-node-css-style                        "-->\n"                           "</style>\n"))                        "</head>\n"                        "<body>\n"))        (let ((begin-html-mark (regexp-quote "#+BEGIN_HTML"))              (end-html-mark   (regexp-quote "#+END_HTML"))              head              end-pos              end-pos-match              )          ;; Take care of #+BEGIN_HTML - #+END_HTML          (while (string-match begin-html-mark text)            (setq head (substring text 0 (match-beginning 0)))            (setq end-pos-match (match-end 0))            (setq node-res (concat node-res                                   (org-freemind-convert-text-p head)))            (setq text (substring text end-pos-match))            (setq end-pos (string-match end-html-mark text))            (if end-pos                (setq end-pos-match (match-end 0))              (message "org-freemind: Missing #+END_HTML")              (setq end-pos (length text))              (setq end-pos-match end-pos))            (setq node-res (concat node-res                                   (substring text 0 end-pos)))            (setq text (substring text end-pos-match)))          (setq node-res (concat node-res                                 (org-freemind-convert-text-p text))))        (setq node-res (concat                        node-res                        "</body>\n"                        "</html>\n"                        "</richcontent>\n"                        ;; Put a note that this is for the parent node                        ;; "<richcontent TYPE=\"NOTE\"><html>"                        ;; "<head>"                        ;; "</head>"                        ;; "<body>"                        ;; "<p>"                        ;; "-- This is more about \"" node-name "\" --"                        ;; "</p>"                        ;; "</body>"                        ;; "</html>"                        ;; "</richcontent>\n"                        note-res                        "</node>\n" ;; ok                        )))      (list node-res note-res))))(defun org-freemind-write-node (mm-buffer drawers-regexp                                num-left-nodes base-level                                current-level next-level this-m2                                this-node-end                                this-children-visible                                next-node-start                                next-has-some-visible-child)  (let* (this-icons         this-bg-color         this-m2-escaped         this-rich-node         this-rich-note         )    (when (string-match "TODO" this-m2)      (setq this-m2 (replace-match "" nil nil this-m2))      (add-to-list 'this-icons "button_cancel")      (setq this-bg-color "#ffff88")      (when (string-match "\\[#\\(.\\)\\]" this-m2)        (let ((prior (string-to-char (match-string 1 this-m2))))          (setq this-m2 (replace-match "" nil nil this-m2))          (cond           ((= prior ?A)            (add-to-list 'this-icons "full-1")            (setq this-bg-color "#ff0000"))           ((= prior ?B)            (add-to-list 'this-icons "full-2")            (setq this-bg-color "#ffaa00"))           ((= prior ?C)            (add-to-list 'this-icons "full-3")            (setq this-bg-color "#ffdd00"))           ((= prior ?D)            (add-to-list 'this-icons "full-4")            (setq this-bg-color "#ffff00"))           ((= prior ?E)            (add-to-list 'this-icons "full-5"))           ((= prior ?F)            (add-to-list 'this-icons "full-6"))           ((= prior ?G)            (add-to-list 'this-icons "full-7"))           ))))    (setq this-m2 (org-trim this-m2))    (setq this-m2-escaped (org-freemind-escape-str-from-org this-m2))    (let ((node-notes (org-freemind-org-text-to-freemind-subnode/note                       this-m2-escaped                       this-node-end                       (1- next-node-start)                       drawers-regexp)))      (setq this-rich-node (nth 0 node-notes))      (setq this-rich-note (nth 1 node-notes)))    (with-current-buffer mm-buffer      (insert "<node text=\"" this-m2-escaped "\"")      (org-freemind-get-node-style this-m2)      (when (> next-level current-level)        (unless (or this-children-visible                    next-has-some-visible-child)          (insert " folded=\"true\"")))      (when (and (= current-level (1+ base-level))                 (> num-left-nodes 0))        (setq num-left-nodes (1- num-left-nodes))        (insert " position=\"left\""))      (when this-bg-color        (insert " background_color=\"" this-bg-color "\""))      (insert ">\n")      (when this-icons        (dolist (icon this-icons)          (insert "<icon builtin=\"" icon "\"/>\n")))      )    (with-current-buffer mm-buffer      ;;(when this-rich-note (insert this-rich-note))      (when this-rich-node (insert this-rich-node))))  num-left-nodes)(defun org-freemind-check-overwrite (file interactively)  "Check if file FILE already exists.If FILE does not exists return t.If INTERACTIVELY is non-nil ask if the file should be replacedand return t/nil if it should/should not be replaced.Otherwise give an error say the file exists."  (if (file-exists-p file)      (if interactively          (y-or-n-p (format "File %s exists, replace it? " file))        (error "File %s already exists" file))    t))(defvar org-freemind-node-pattern  ;;(rx bol  ;;    (submatch (1+ "*"))  ;;    (1+ space)  ;;    (submatch (*? nonl))  ;;    eol)  "^\\(\\*+\\)[[:space:]]+\\(.*?\\)$")(defun org-freemind-look-for-visible-child (node-level)  (save-excursion    (save-match-data      (let ((found-visible-child nil))        (while (and (not found-visible-child)                    (re-search-forward org-freemind-node-pattern nil t))          (let* ((m1 (match-string-no-properties 1))                 (level (length m1)))            (if (>= node-level level)                (setq found-visible-child 'none)              (unless (get-char-property (line-beginning-position) 'invisible)                (setq found-visible-child 'found)))))        (eq found-visible-child 'found)        ))))(defun org-freemind-goto-line (line)  "Go to line number LINE."  (save-restriction    (widen)    (goto-char (point-min))    (forward-line (1- line))))(defun org-freemind-write-mm-buffer (org-buffer mm-buffer node-at-line)  (with-current-buffer org-buffer    (dolist (node-style org-freemind-node-styles)      (when (org-string-match-p (car node-style) buffer-file-name)        (setq org-freemind-node-style (cadr node-style))))    ;;(message "org-freemind-node-style =%s" org-freemind-node-style)    (save-match-data      (let* ((drawers (copy-sequence org-drawers))             drawers-regexp             (num-top1-nodes 0)             (num-top2-nodes 0)             num-left-nodes             (unclosed-nodes 0)	     (odd-only org-odd-levels-only)             (first-time t)             (current-level 1)             base-level             prev-node-end             rich-text             unfinished-tag             node-at-line-level             node-at-line-last)        (with-current-buffer mm-buffer          (erase-buffer)          (setq buffer-file-coding-system 'utf-8)          ;; Fix-me: Currently Freemind (ver 0.9.0 RC9) does not support this:          ;;(insert "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")          (insert "<map version=\"0.9.0\">\n")          (insert "<!-- To view this file, download free mind mapping software FreeMind from http://freemind.sourceforge.net -->\n"))        (save-excursion          ;; Get special buffer vars:          (goto-char (point-min))          (message "Writing Freemind file...")          (while (re-search-forward "^#\\+DRAWERS:" nil t)            (let ((dr-txt (buffer-substring-no-properties (match-end 0) (line-end-position))))              (setq drawers (append drawers (split-string dr-txt) nil))))          (setq drawers-regexp                (concat "^[[:blank:]]*:"                        (regexp-opt drawers)                        ;;(rx ":" (0+ blank)                        ;;    "\n"                        ;;    (*? anything)                        ;;    "\n"                        ;;    (0+ blank)                        ;;    ":END:"                        ;;    (0+ blank)                        ;;    eol)			":[[:blank:]]*\n\\(?:.\\|\n\\)*?\n[[:blank:]]*:END:[[:blank:]]*$"			))          (if node-at-line              ;; Get number of top nodes and last line for this node              (progn                (org-freemind-goto-line node-at-line)                (unless (looking-at org-freemind-node-pattern)                  (error "No node at line %s" node-at-line))                (setq node-at-line-level (length (match-string-no-properties 1)))                (forward-line)                (setq node-at-line-last                      (catch 'last-line                        (while (re-search-forward org-freemind-node-pattern nil t)                          (let* ((m1 (match-string-no-properties 1))                                 (level (length m1)))                            (if (<= level node-at-line-level)                                (progn                                  (beginning-of-line)                                  (throw 'last-line (1- (point))))                              (if (= level (1+ node-at-line-level))                                  (setq num-top2-nodes (1+ num-top2-nodes))))))))                (setq current-level node-at-line-level)                (setq num-top1-nodes 1)                (org-freemind-goto-line node-at-line))            ;; First get number of top nodes            (goto-char (point-min))            (while (re-search-forward org-freemind-node-pattern nil t)              (let* ((m1 (match-string-no-properties 1))                     (level (length m1)))                (if (= level 1)                    (setq num-top1-nodes (1+ num-top1-nodes))                  (if (= level 2)                      (setq num-top2-nodes (1+ num-top2-nodes))))))            ;; If there is more than one top node we need to insert a node            ;; to keep them together.            (goto-char (point-min))            (when (> num-top1-nodes 1)              (setq num-top2-nodes num-top1-nodes)              (setq current-level 0)              (let ((orig-name (if buffer-file-name                                   (file-name-nondirectory (buffer-file-name))                                 (buffer-name))))                (with-current-buffer mm-buffer                  (insert "<node text=\"" orig-name "\" background_color=\"#00bfff\">\n"                          ;; Put a note that this is for the parent node                          "<richcontent TYPE=\"NOTE\"><html>"                          "<head>"                          "</head>"                          "<body>"                          "<p>"                          org-freemind-org-nfix "WHOLE FILE"                          "</p>"                          "</body>"                          "</html>"                          "</richcontent>\n")))))          (setq num-left-nodes (floor num-top2-nodes 2))          (setq base-level current-level)          (let (this-m2                this-node-end                this-children-visible                next-m2                next-node-start                next-level                next-has-some-visible-child                next-children-visible                )            (while (and                    (re-search-forward org-freemind-node-pattern nil t)                    (if node-at-line-last (<= (point) node-at-line-last) t)                    )              (let* ((next-m1 (match-string-no-properties 1))                     (next-node-end (match-end 0))                     )                (setq next-node-start (match-beginning 0))                (setq next-m2 (match-string-no-properties 2))                (setq next-level (length next-m1))                (setq next-children-visible                      (not (eq 'outline                               (get-char-property (line-end-position) 'invisible))))                (setq next-has-some-visible-child                      (if next-children-visible t                        (org-freemind-look-for-visible-child next-level)))                (when this-m2                  (setq num-left-nodes (org-freemind-write-node mm-buffer drawers-regexp num-left-nodes base-level current-level next-level this-m2 this-node-end this-children-visible next-node-start next-has-some-visible-child)))                (when (if (= num-top1-nodes 1) (> current-level base-level) t)                  (while (>= current-level next-level)                    (with-current-buffer mm-buffer                      (insert "</node>\n")                      (setq current-level			    (- current-level (if odd-only 2 1))))))                (setq this-node-end (1+ next-node-end))                (setq this-m2 next-m2)                (setq current-level next-level)                (setq this-children-visible next-children-visible)                (forward-char)                ));;;             (unless (if node-at-line-last;;;                         (>= (point) node-at-line-last);;;                       nil)              ;; Write last node:              (setq this-m2 next-m2)              (setq current-level next-level)              (setq next-node-start (if node-at-line-last                                        (1+ node-at-line-last)                                      (point-max)))              (setq num-left-nodes (org-freemind-write-node mm-buffer drawers-regexp num-left-nodes base-level current-level next-level this-m2 this-node-end this-children-visible next-node-start next-has-some-visible-child))              (with-current-buffer mm-buffer (insert "</node>\n"))              ;)            )          (with-current-buffer mm-buffer            (while (> current-level base-level)              (insert "</node>\n")	      (setq current-level		    (- current-level (if odd-only 2 1)))              ))          (with-current-buffer mm-buffer            (insert "</map>")            (delete-trailing-whitespace)            (goto-char (point-min))            ))))))(defun org-freemind-get-node-style (node-name)  "NOT READY YET."  ;;<node BACKGROUND_COLOR="#eeee00" CREATED="1234668815593" MODIFIED="1234668815593" STYLE="bubble">  ;;<font BOLD="true" NAME="SansSerif" SIZE="12"/>  (let (node-styles        node-style)    (dolist (style-list org-freemind-node-style)      (let ((node-regexp (car style-list)))        (message "node-regexp=%s node-name=%s" node-regexp node-name)        (when (org-string-match-p node-regexp node-name)          ;;(setq node-style (org-freemind-do-apply-node-style style-list))          (setq node-style (cadr style-list))          (when node-style            (message "node-style=%s" node-style)            (setq node-styles (append node-styles node-style)))          )))))(defun org-freemind-do-apply-node-style (style-list)  (message "style-list=%S" style-list)  (let ((node-style 'fork)        (color "red")        (background-color "yellow")        (edge-color "green")        (edge-style 'bezier)        (edge-width 'thin)        (italic t)        (bold t)        (font-name "SansSerif")        (font-size 12))    (dolist (style (cadr style-list))      (message "    style=%s" style)      (let ((what (car style)))        (cond         ((eq what 'node-style)          (setq node-style (cadr style)))         ((eq what 'color)          (setq color (cadr style)))         ((eq what 'background-color)          (setq background-color (cadr style)))         ((eq what 'edge-color)          (setq edge-color (cadr style)))         ((eq what 'edge-style)          (setq edge-style (cadr style)))         ((eq what 'edge-width)          (setq edge-width (cadr style)))         ((eq what 'italic)          (setq italic (cadr style)))         ((eq what 'bold)          (setq bold (cadr style)))         ((eq what 'font-name)          (setq font-name (cadr style)))         ((eq what 'font-size)          (setq font-size (cadr style)))         )        (insert (format " style=\"%s\"" node-style))        (insert (format " color=\"%s\"" color))        (insert (format " background_color=\"%s\"" background-color))        (insert ">\n")        (insert "<edge")        (insert (format " color=\"%s\"" edge-color))        (insert (format " style=\"%s\"" edge-style))        (insert (format " width=\"%s\"" edge-width))        (insert "/>\n")        (insert "<font")        (insert (format " italic=\"%s\"" italic))        (insert (format " bold=\"%s\"" bold))        (insert (format " name=\"%s\"" font-name))        (insert (format " size=\"%s\"" font-size))        ))));;;###autoload(defun org-freemind-from-org-mode-node (node-line mm-file)  "Convert node at line NODE-LINE to the FreeMind file MM-FILE.See `org-freemind-from-org-mode' for more information."  (interactive   (progn     (unless (org-back-to-heading nil)       (error "Can't find org-mode node start"))     (let* ((line (line-number-at-pos))            (default-mm-file (concat (if buffer-file-name                                         (file-name-nondirectory buffer-file-name)                                       "nofile")                                     "-line-" (number-to-string line)                                     ".mm"))            (mm-file (read-file-name "Output FreeMind file: " nil nil nil default-mm-file)))       (list line mm-file))))  (when (org-freemind-check-overwrite mm-file (org-called-interactively-p 'any))    (let ((org-buffer (current-buffer))          (mm-buffer (find-file-noselect mm-file)))      (org-freemind-write-mm-buffer org-buffer mm-buffer node-line)      (with-current-buffer mm-buffer        (basic-save-buffer)        (when (org-called-interactively-p 'any)          (switch-to-buffer-other-window mm-buffer)          (when (y-or-n-p "Show in FreeMind? ")            (org-freemind-show buffer-file-name)))))));;;###autoload(defun org-freemind-from-org-mode (org-file mm-file)  "Convert the `org-mode' file ORG-FILE to the FreeMind file MM-FILE.All the nodes will be opened or closed in Freemind just as youhave them in `org-mode'.Note that exporting to Freemind also gives you an alternative wayto export from `org-mode' to html.  You can create a dynamic htmlversion of the your org file, by first exporting to Freemind andthen exporting from Freemind to html.  The 'AsXHTML (JavaScript)' version in Freemind works very well \(and youcan use a CSS stylesheet to style it)."  ;; Fix-me: better doc, include recommendations etc.  (interactive   (let* ((org-file buffer-file-name)          (default-mm-file (concat                            (if org-file                                (file-name-nondirectory org-file)                              "nofile")                            ".mm"))          (mm-file (read-file-name "Output FreeMind file: " nil nil nil default-mm-file)))     (list org-file mm-file)))  (when (org-freemind-check-overwrite mm-file (org-called-interactively-p 'any))    (let ((org-buffer (if org-file (find-file-noselect org-file) (current-buffer)))          (mm-buffer (find-file-noselect mm-file)))      (org-freemind-write-mm-buffer org-buffer mm-buffer nil)      (with-current-buffer mm-buffer        (basic-save-buffer)        (when (org-called-interactively-p 'any)          (switch-to-buffer-other-window mm-buffer)          (when (y-or-n-p "Show in FreeMind? ")            (org-freemind-show buffer-file-name)))))));;;###autoload(defun org-freemind-from-org-sparse-tree (org-buffer mm-file)  "Convert visible part of buffer ORG-BUFFER to FreeMind file MM-FILE."  (interactive   (let* ((org-file buffer-file-name)          (default-mm-file (concat                            (if org-file                                (file-name-nondirectory org-file)                              "nofile")                            "-sparse.mm"))          (mm-file (read-file-name "Output FreeMind file: " nil nil nil default-mm-file)))     (list (current-buffer) mm-file)))  (when (org-freemind-check-overwrite mm-file (org-called-interactively-p 'any))    (let (org-buffer          (mm-buffer (find-file-noselect mm-file)))      (save-window-excursion        (org-export-visible ?\  nil)        (setq org-buffer (current-buffer)))      (org-freemind-write-mm-buffer org-buffer mm-buffer nil)      (with-current-buffer mm-buffer        (basic-save-buffer)        (when (org-called-interactively-p 'any)          (switch-to-buffer-other-window mm-buffer)          (when (y-or-n-p "Show in FreeMind? ")            (org-freemind-show buffer-file-name)))))));;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; FreeMind => Org;; (sort '(b a c) 'org-freemind-lt-symbols)(defun org-freemind-lt-symbols (sym-a sym-b)  (string< (symbol-name sym-a) (symbol-name sym-b)));; (sort '((b . 1) (a . 2) (c . 3)) 'org-freemind-lt-xml-attrs)(defun org-freemind-lt-xml-attrs (attr-a attr-b)  (string< (symbol-name (car attr-a)) (symbol-name (car attr-b))));; xml-parse-region gives things like;; ((p nil "\n";;     (a;;      ((href . "link"));;      "text");;     "\n";;     (b nil "hej");;     "\n"));; '(a . nil);; (org-freemind-symbols= 'a (car '(A B)))(defsubst org-freemind-symbols= (sym-a sym-b)  "Return t if downcased names of SYM-A and SYM-B are equal.SYM-A and SYM-B should be symbols."  (or (eq sym-a sym-b)      (string= (downcase (symbol-name sym-a))               (downcase (symbol-name sym-b)))))(defun org-freemind-get-children (parent path)  "Find children node to PARENT from PATH.PATH should be a list of steps, where each step has the form  '(NODE-NAME (ATTR-NAME . ATTR-VALUE))"  ;; Fix-me: maybe implement op? step: Name, number, attr, attr op val  ;; Fix-me: case insensitive version for children?  (let* ((children (if (not (listp (car parent)))                       (cddr parent)                     (let (cs)                       (dolist (p parent)                         (dolist (c (cddr p))                           (add-to-list 'cs c)))                       cs)                     ))         (step (car path))         (step-node (if (listp step) (car step) step))         (step-attr-list (when (listp step) (sort (cdr step) 'org-freemind-lt-xml-attrs)))         (path-tail (cdr path))         path-children)    (dolist (child children)      ;; skip xml.el formatting nodes      (unless (stringp child)        ;; compare node name        (when (if (not step-node)                  t ;; any node name                (org-freemind-symbols= step-node (car child)))          (if (not step-attr-list)              ;;(throw 'path-child child) ;; no attr to care about              (add-to-list 'path-children child)            (let* ((child-attr-list (cadr child))                   (step-attr-copy (copy-sequence step-attr-list)))              (dolist (child-attr child-attr-list)                                   ;; Compare attr names:                (when (org-freemind-symbols= (caar step-attr-copy) (car child-attr))                  ;; Compare values:                  (let ((step-val (cdar step-attr-copy))                        (child-val (cdr child-attr)))                    (when (if (not step-val)                              t ;; any value                            (string= step-val child-val))                      (setq step-attr-copy (cdr step-attr-copy))))))              ;; Did we find all?              (unless step-attr-copy                ;;(throw 'path-child child)                (add-to-list 'path-children child)                ))))))    (if path-tail        (org-freemind-get-children path-children path-tail)      path-children)))(defun org-freemind-get-richcontent-node (node)  (let ((rc-nodes         (org-freemind-get-children node '((richcontent (type . "NODE")) html body))))    (when (> (length rc-nodes) 1)      (lwarn t :warning "Unexpected structure: several <richcontent type=\"NODE\" ...>"))    (car rc-nodes)))(defun org-freemind-get-richcontent-note (node)  (let ((rc-notes         (org-freemind-get-children node '((richcontent (type . "NOTE")) html body))))    (when (> (length rc-notes) 1)      (lwarn t :warning "Unexpected structure: several <richcontent type=\"NOTE\" ...>"))    (car rc-notes)))(defun org-freemind-test-get-tree-text ()  (let ((node '(p nil "\n"                 (a                  ((href . "link"))                  "text")                 "\n"                 (b nil "hej")                 "\n")))    (org-freemind-get-tree-text node)));; (org-freemind-test-get-tree-text)(defun org-freemind-get-tree-text (node)  (when node    (let ((ntxt "")          (link nil)          (lf-after nil))      (dolist (n node)        (case n          ;;(a (setq is-link t) )          ((h1 h2 h3 h4 h5 h6 p)           ;;(setq ntxt (concat "\n" ntxt))           (setq lf-after 2)           )          (br           (setq lf-after 1)           )          (t           (cond            ((stringp n)             (when (string= n "\n") (setq n ""))             (if link                 (setq ntxt (concat ntxt                                    "[[" link "][" n "]]"))               (setq ntxt (concat ntxt n))))            ((and n (listp n))             (if (symbolp (car n))                 (setq ntxt (concat ntxt (org-freemind-get-tree-text n)))               ;; This should be the attributes:               (dolist (att-val n)                 (let ((att (car att-val))                       (val (cdr att-val)))                   (when (eq att 'href)                     (setq link val)))))             )))))      (if lf-after          (setq ntxt (concat ntxt (make-string lf-after ?\n)))        (setq ntxt (concat ntxt " ")))      ;;(setq ntxt (concat ntxt (format "{%s}" n)))      ntxt)))(defun org-freemind-get-richcontent-node-text (node)  "Get the node text as from the richcontent node NODE."  (save-match-data    (let* ((rc (org-freemind-get-richcontent-node node))           (txt (org-freemind-get-tree-text rc)))      ;;(when txt (setq txt (replace-regexp-in-string "[[:space:]]+" " " txt)))      txt      )))(defun org-freemind-get-richcontent-note-text (node)  "Get the node text as from the richcontent note NODE."  (save-match-data    (let* ((rc (org-freemind-get-richcontent-note node))           (txt (when rc (org-freemind-get-tree-text rc))))      ;;(when txt (setq txt (replace-regexp-in-string "[[:space:]]+" " " txt)))      txt      )))(defun org-freemind-get-icon-names (node)  (let* ((icon-nodes (org-freemind-get-children node '((icon ))))         names)    (dolist (icn icon-nodes)      (setq names (cons (cdr (assq 'builtin (cadr icn))) names)))    ;; (icon (builtin . "full-1"))    names))(defun org-freemind-node-to-org (node level skip-levels)  (let ((qname (car node))        (attributes (cadr node))        text        ;; Fix-me: note is never inserted        (note (org-freemind-get-richcontent-note-text node))        (mark "-- This is more about ")        (icons (org-freemind-get-icon-names node))        (children (cddr node)))    (when (< 0 (- level skip-levels))      (dolist (attrib attributes)        (case (car attrib)          ('TEXT (setq text (cdr attrib)))          ('text (setq text (cdr attrib)))))      (unless text        ;; There should be a richcontent node holding the text:        (setq text (org-freemind-get-richcontent-node-text node)))      (when icons        (when (member "full-1" icons) (setq text (concat "[#A] " text)))        (when (member "full-2" icons) (setq text (concat "[#B] " text)))        (when (member "full-3" icons) (setq text (concat "[#C] " text)))        (when (member "full-4" icons) (setq text (concat "[#D] " text)))        (when (member "full-5" icons) (setq text (concat "[#E] " text)))        (when (member "full-6" icons) (setq text (concat "[#F] " text)))        (when (member "full-7" icons) (setq text (concat "[#G] " text)))        (when (member "button_cancel" icons) (setq text (concat "TODO " text)))        )      (if (and note               (string= mark (substring note 0 (length mark))))          (progn            (setq text (replace-regexp-in-string "\n $" "" text))            (insert text))        (case qname          ('node           (insert (make-string (- level skip-levels) ?*) " " text "\n")           (when note             (insert ":COMMENT:\n" note "\n:END:\n"))           ))))    (dolist (child children)      (unless (or (null child)                  (stringp child))        (org-freemind-node-to-org child (1+ level) skip-levels)))));; Fix-me: put back special things, like drawers that are stored in;; the notes. Should maybe all notes contents be put in drawers?;;;###autoload(defun org-freemind-to-org-mode (mm-file org-file)  "Convert FreeMind file MM-FILE to `org-mode' file ORG-FILE."  (interactive   (save-match-data     (let* ((mm-file (buffer-file-name))            (default-org-file (concat (file-name-nondirectory mm-file) ".org"))            (org-file (read-file-name "Output org-mode file: " nil nil nil default-org-file)))       (list mm-file org-file))))  (when (org-freemind-check-overwrite org-file (org-called-interactively-p 'any))    (let ((mm-buffer (find-file-noselect mm-file))          (org-buffer (find-file-noselect org-file)))      (with-current-buffer mm-buffer        (let* ((xml-list (xml-parse-file mm-file))               (top-node (cadr (cddar xml-list)))               (note (org-freemind-get-richcontent-note-text top-node))               (skip-levels                (if (and note                         (string-match "^--org-mode: WHOLE FILE$" note))                    1                  0)))          (with-current-buffer org-buffer            (erase-buffer)            (org-freemind-node-to-org top-node 1 skip-levels)            (goto-char (point-min))            (org-set-tags t t) ;; Align all tags            )          (switch-to-buffer-other-window org-buffer)          )))))(provide 'org-freemind);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; org-freemind.el ends here
 |