org-freemind.el 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079
  1. ;;; org-freemind.el --- Export Org files to freemind
  2. ;; Copyright (C) 2009 Free Software Foundation, Inc.
  3. ;; Author: Lennart Borgman (lennart O borgman A gmail O com)
  4. ;; Keywords: outlines, hypermedia, calendar, wp
  5. ;; Homepage: http://orgmode.org
  6. ;; Version: 6.32trans
  7. ;;
  8. ;; This file is part of GNU Emacs.
  9. ;;
  10. ;; GNU Emacs is free software: you can redistribute it and/or modify
  11. ;; it under the terms of the GNU General Public License as published by
  12. ;; the Free Software Foundation, either version 3 of the License, or
  13. ;; (at your option) any later version.
  14. ;; GNU Emacs is distributed in the hope that it will be useful,
  15. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;; GNU General Public License for more details.
  18. ;; You should have received a copy of the GNU General Public License
  19. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  20. ;; --------------------------------------------------------------------
  21. ;; Features that might be required by this library:
  22. ;;
  23. ;; `backquote', `bytecomp', `cl', `easymenu', `font-lock',
  24. ;; `noutline', `org', `org-compat', `org-faces', `org-footnote',
  25. ;; `org-list', `org-macs', `org-src', `outline', `syntax',
  26. ;; `time-date', `xml'.
  27. ;;
  28. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  29. ;;
  30. ;;; Commentary:
  31. ;;
  32. ;; This file tries to implement some functions useful for
  33. ;; transformation between org-mode and FreeMind files.
  34. ;;
  35. ;; Here are the commands you can use:
  36. ;;
  37. ;; M-x `org-freemind-from-org-mode'
  38. ;; M-x `org-freemind-from-org-mode-node'
  39. ;; M-x `org-freemind-from-org-sparse-tree'
  40. ;;
  41. ;; M-x `org-freemind-to-org-mode'
  42. ;;
  43. ;; M-x `org-freemind-show'
  44. ;;
  45. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  46. ;;
  47. ;;; Change log:
  48. ;;
  49. ;; 2009-02-15: Added check for next level=current+1
  50. ;; 2009-02-21: Fixed bug in `org-freemind-to-org-mode'.
  51. ;; 2009-10-25: Added support for `org-odd-levels-only'.
  52. ;; Added y/n question before showing in FreeMind.
  53. ;;
  54. ;;
  55. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  56. ;;
  57. ;; This program is free software; you can redistribute it and/or
  58. ;; modify it under the terms of the GNU General Public License as
  59. ;; published by the Free Software Foundation; either version 2, or
  60. ;; (at your option) any later version.
  61. ;;
  62. ;; This program is distributed in the hope that it will be useful,
  63. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  64. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  65. ;; General Public License for more details.
  66. ;;
  67. ;; You should have received a copy of the GNU General Public License
  68. ;; along with this program; see the file COPYING. If not, write to
  69. ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
  70. ;; Floor, Boston, MA 02110-1301, USA.
  71. ;;
  72. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  73. ;;
  74. ;;; Code:
  75. (require 'xml)
  76. (require 'org)
  77. (eval-when-compile (require 'cl))
  78. ;; Fix-me: I am not sure these are useful:
  79. ;;
  80. ;; (defcustom org-freemind-main-fgcolor "black"
  81. ;; "Color of main node's text."
  82. ;; :type 'color
  83. ;; :group 'freemind)
  84. ;; (defcustom org-freemind-main-color "black"
  85. ;; "Background color of main node."
  86. ;; :type 'color
  87. ;; :group 'freemind)
  88. ;; (defcustom org-freemind-child-fgcolor "black"
  89. ;; "Color of child nodes' text."
  90. ;; :type 'color
  91. ;; :group 'freemind)
  92. ;; (defcustom org-freemind-child-color "black"
  93. ;; "Background color of child nodes."
  94. ;; :type 'color
  95. ;; :group 'freemind)
  96. (defvar org-freemind-node-style nil "Internal use.")
  97. (defcustom org-freemind-node-styles nil
  98. "Styles to apply to node.
  99. NOT READY YET."
  100. :type '(repeat
  101. (list :tag "Node styles for file"
  102. (regexp :tag "File name")
  103. (repeat
  104. (list :tag "Node"
  105. (regexp :tag "Node name regexp")
  106. (set :tag "Node properties"
  107. (list :format "%v" (const :format "" node-style)
  108. (choice :tag "Style"
  109. :value bubble
  110. (const bubble)
  111. (const fork)))
  112. (list :format "%v" (const :format "" color)
  113. (color :tag "Color" :value "red"))
  114. (list :format "%v" (const :format "" background-color)
  115. (color :tag "Background color" :value "yellow"))
  116. (list :format "%v" (const :format "" edge-color)
  117. (color :tag "Edge color" :value "green"))
  118. (list :format "%v" (const :format "" edge-style)
  119. (choice :tag "Edge style" :value bezier
  120. (const :tag "Linear" linear)
  121. (const :tag "Bezier" bezier)
  122. (const :tag "Sharp Linear" sharp-linear)
  123. (const :tag "Sharp Bezier" sharp-bezier)))
  124. (list :format "%v" (const :format "" edge-width)
  125. (choice :tag "Edge width" :value thin
  126. (const :tag "Parent" parent)
  127. (const :tag "Thin" thin)
  128. (const 1)
  129. (const 2)
  130. (const 4)
  131. (const 8)))
  132. (list :format "%v" (const :format "" italic)
  133. (const :tag "Italic font" t))
  134. (list :format "%v" (const :format "" bold)
  135. (const :tag "Bold font" t))
  136. (list :format "%v" (const :format "" font-name)
  137. (string :tag "Font name" :value "SansSerif"))
  138. (list :format "%v" (const :format "" font-size)
  139. (integer :tag "Font size" :value 12)))))))
  140. :group 'freemind)
  141. ;;;###autoload
  142. (defun org-export-as-freemind (arg &optional hidden ext-plist
  143. to-buffer body-only pub-dir)
  144. (interactive "P")
  145. (let* ((opt-plist (org-combine-plists (org-default-export-plist)
  146. ext-plist
  147. (org-infile-export-plist)))
  148. (region-p (org-region-active-p))
  149. (rbeg (and region-p (region-beginning)))
  150. (rend (and region-p (region-end)))
  151. (subtree-p
  152. (if (plist-get opt-plist :ignore-subree-p)
  153. nil
  154. (when region-p
  155. (save-excursion
  156. (goto-char rbeg)
  157. (and (org-at-heading-p)
  158. (>= (org-end-of-subtree t t) rend))))))
  159. (opt-plist (setq org-export-opt-plist
  160. (if subtree-p
  161. (org-export-add-subtree-options opt-plist rbeg)
  162. opt-plist)))
  163. (bfname (buffer-file-name (or (buffer-base-buffer) (current-buffer))))
  164. (filename (concat (file-name-as-directory
  165. (or pub-dir
  166. (org-export-directory :ascii opt-plist)))
  167. (file-name-sans-extension
  168. (or (and subtree-p
  169. (org-entry-get (region-beginning)
  170. "EXPORT_FILE_NAME" t))
  171. (file-name-nondirectory bfname)))
  172. ".mm")))
  173. (when (file-exists-p filename)
  174. (delete-file filename))
  175. (cond
  176. (subtree-p
  177. (org-freemind-from-org-mode-node (line-number-at-pos rbeg)
  178. filename))
  179. (t (org-freemind-from-org-mode bfname filename)))))
  180. ;;;###autoload
  181. (defun org-freemind-show (mm-file)
  182. "Show file MM-FILE in Freemind."
  183. (interactive
  184. (list
  185. (save-match-data
  186. (let ((name (read-file-name "FreeMind file: "
  187. nil nil nil
  188. (if (buffer-file-name)
  189. (file-name-nondirectory (buffer-file-name))
  190. "")
  191. ;; Fix-me: Is this an Emacs bug?
  192. ;; This predicate function is never
  193. ;; called.
  194. (lambda (fn)
  195. (string-match "^mm$" (file-name-extension fn))))))
  196. (setq name (expand-file-name name))
  197. name))))
  198. (org-open-file mm-file))
  199. (defconst org-freemind-org-nfix "--org-mode: ")
  200. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  201. ;;; Format converters
  202. (defun org-freemind-escape-str-from-org (org-str)
  203. (let ((chars (append org-str nil))
  204. (fm-str ""))
  205. (dolist (cc chars)
  206. (setq fm-str
  207. (concat fm-str
  208. (if (< cc 256)
  209. (cond
  210. ((= cc ?\") "&quot;")
  211. ((= cc ?\&) "&amp;")
  212. ((= cc ?\<) "&lt;")
  213. ((= cc ?\>) "&gt;")
  214. (t (char-to-string cc)))
  215. ;; Formatting as &#number; is maybe needed
  216. ;; according to a bug report from kazuo
  217. ;; fujimoto, but I have now instead added a xml
  218. ;; processing instruction saying that the mm
  219. ;; file is utf-8:
  220. ;;
  221. ;; (format "&#x%x;" (- cc ;; ?\x800))
  222. (char-to-string cc)
  223. ))))
  224. fm-str))
  225. (defun org-freemind-unescape-str-to-org (fm-str)
  226. (let ((org-str fm-str))
  227. (setq org-str (replace-regexp-in-string "&quot;" "\"" org-str))
  228. (setq org-str (replace-regexp-in-string "&amp;" "&" org-str))
  229. (setq org-str (replace-regexp-in-string "&lt;" "<" org-str))
  230. (setq org-str (replace-regexp-in-string "&gt;" ">" org-str))
  231. (setq org-str (replace-regexp-in-string
  232. "&#x\\([a-f0-9]\\{2\\}\\);"
  233. (lambda (m)
  234. (char-to-string (+ (string-to-number (match-string 1 str) 16)
  235. ?\x800)))
  236. org-str))))
  237. ;; (org-freemind-test-escape)
  238. ;; (defun org-freemind-test-escape ()
  239. ;; (let* ((str1 "a quote: \", an amp: &, lt: <; over 256: öåäÖÅÄ")
  240. ;; (str2 (org-freemind-escape-str-from-org str1))
  241. ;; (str3 (org-freemind-unescape-str-to-org str2))
  242. ;; )
  243. ;; (unless (string= str1 str3)
  244. ;; (error "str3=%s" str3))
  245. ;; ))
  246. (defun org-freemind-convert-links-from-org (org-str)
  247. (let ((fm-str (replace-regexp-in-string
  248. (rx (not (any "[\""))
  249. (submatch
  250. "http"
  251. (opt ?\s)
  252. "://"
  253. (1+
  254. (any "-%.?@a-zA-Z0-9()_/:~=&#"))))
  255. "[[\\1][\\1]]"
  256. org-str)))
  257. (replace-regexp-in-string (rx "[["
  258. (submatch (*? nonl))
  259. "]["
  260. (submatch (*? nonl))
  261. "]]")
  262. "<a href=\"\\1\">\\2</a>"
  263. fm-str)))
  264. ;;(org-freemind-convert-links-to-org "<a href=\"http://www.somewhere/\">link-text</a>")
  265. (defun org-freemind-convert-links-to-org (fm-str)
  266. (let ((org-str (replace-regexp-in-string
  267. (rx "<a"
  268. space
  269. (0+
  270. (0+ (not (any ">")))
  271. space)
  272. "href=\""
  273. (submatch (0+ (not (any "\""))))
  274. "\""
  275. (0+ (not (any ">")))
  276. ">"
  277. (submatch (0+ (not (any "<"))))
  278. "</a>")
  279. "[[\\1][\\2]]"
  280. fm-str)))
  281. org-str))
  282. (defun org-freemind-convert-drawers-from-org (text)
  283. )
  284. ;; (org-freemind-test-links)
  285. ;; (defun org-freemind-test-links ()
  286. ;; (let* ((str1 "[[http://www.somewhere/][link-text]")
  287. ;; (str2 (org-freemind-convert-links-from-org str1))
  288. ;; (str3 (org-freemind-convert-links-to-org str2))
  289. ;; )
  290. ;; (unless (string= str1 str3)
  291. ;; (error "str3=%s" str3))
  292. ;; ))
  293. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  294. ;;; Org => FreeMind
  295. (defun org-freemind-org-text-to-freemind-subnode/note (node-name start end)
  296. ;; fix-me: doc
  297. (let ((text (buffer-substring-no-properties start end))
  298. (node-res "")
  299. (note-res ""))
  300. (save-match-data
  301. (setq text (org-freemind-escape-str-from-org text))
  302. ;; First see if there is something that should be moved to the
  303. ;; note part:
  304. (let (drawers)
  305. (while (string-match drawers-regexp text)
  306. (setq drawers (cons (match-string 0 text) drawers))
  307. (setq text
  308. (concat (substring text 0 (match-beginning 0))
  309. (substring text (match-end 0))))
  310. )
  311. (when drawers
  312. (dolist (drawer drawers)
  313. (let ((lines (split-string drawer "\n")))
  314. (dolist (line lines)
  315. (setq note-res (concat
  316. note-res
  317. org-freemind-org-nfix line "<br />\n")))
  318. ))))
  319. (when (> (length note-res) 0)
  320. (setq note-res (concat
  321. "<richcontent TYPE=\"NOTE\"><html>\n"
  322. "<head>\n"
  323. "</head>\n"
  324. "<body>\n"
  325. note-res
  326. "</body>\n"
  327. "</html>\n"
  328. "</richcontent>\n"))
  329. )
  330. ;; There is always an LF char:
  331. (when (> (length text) 1)
  332. (setq node-res (concat
  333. "<node style=\"bubble\" background_color=\"#eeee00\">\n"
  334. "<richcontent TYPE=\"NODE\"><html>\n"
  335. "<head>\n"
  336. "<style type=\"text/css\">\n"
  337. "<!--\n"
  338. "p { margin-top: 0 }\n"
  339. "-->\n"
  340. "</style>\n"
  341. "</head>\n"
  342. "<body>\n"))
  343. (setq node-res (concat node-res "<p>"))
  344. (setq text (replace-regexp-in-string (rx "\n" (0+ blank) "\n") "</p><p>\n" text))
  345. ;;(setq text (replace-regexp-in-string (rx bol (1+ blank) eol) "" text))
  346. ;;(setq text (replace-regexp-in-string (rx bol (1+ blank)) "<br />" text))
  347. (setq text (replace-regexp-in-string "\n" "<br />" text))
  348. (org-freemind-convert-links-from-org text)
  349. (setq node-res (concat node-res text))
  350. (setq node-res (concat node-res "</p>\n"))
  351. (setq node-res (concat
  352. node-res
  353. "</body>\n"
  354. "</html>\n"
  355. "</richcontent>\n"
  356. ;; Put a note that this is for the parent node
  357. "<richcontent TYPE=\"NOTE\"><html>"
  358. "<head>"
  359. "</head>"
  360. "<body>"
  361. "<p>"
  362. "-- This is more about \"" node-name "\" --"
  363. "</p>"
  364. "</body>"
  365. "</html>"
  366. "</richcontent>\n"
  367. "</node>\n" ;; ok
  368. )))
  369. (list node-res note-res))))
  370. (defun org-freemind-write-node (this-m2 this-node-end)
  371. (let* (this-icons
  372. this-bg-color
  373. this-m2-escaped
  374. this-rich-node
  375. this-rich-note
  376. )
  377. (when (string-match "TODO" this-m2)
  378. (setq this-m2 (replace-match "" nil nil this-m2))
  379. (add-to-list 'this-icons "button_cancel")
  380. (setq this-bg-color "#ffff88")
  381. (when (string-match "\\[#\\(.\\)\\]" this-m2)
  382. (let ((prior (string-to-char (match-string 1 this-m2))))
  383. (setq this-m2 (replace-match "" nil nil this-m2))
  384. (cond
  385. ((= prior ?A)
  386. (add-to-list 'this-icons "full-1")
  387. (setq this-bg-color "#ff0000"))
  388. ((= prior ?B)
  389. (add-to-list 'this-icons "full-2")
  390. (setq this-bg-color "#ffaa00"))
  391. ((= prior ?C)
  392. (add-to-list 'this-icons "full-3")
  393. (setq this-bg-color "#ffdd00"))
  394. ((= prior ?D)
  395. (add-to-list 'this-icons "full-4")
  396. (setq this-bg-color "#ffff00"))
  397. ((= prior ?E)
  398. (add-to-list 'this-icons "full-5"))
  399. ((= prior ?F)
  400. (add-to-list 'this-icons "full-6"))
  401. ((= prior ?G)
  402. (add-to-list 'this-icons "full-7"))
  403. ))))
  404. (setq this-m2 (org-trim this-m2))
  405. (setq this-m2-escaped (org-freemind-escape-str-from-org this-m2))
  406. (let ((node-notes (org-freemind-org-text-to-freemind-subnode/note
  407. this-m2-escaped
  408. this-node-end (1- next-node-start))))
  409. (setq this-rich-node (nth 0 node-notes))
  410. (setq this-rich-note (nth 1 node-notes)))
  411. (with-current-buffer mm-buffer
  412. (insert "<node text=\"" this-m2-escaped "\"")
  413. (org-freemind-get-node-style this-m2)
  414. ;;(when (and (> current-level base-level) (> next-level current-level))
  415. (when (> next-level current-level)
  416. (unless (or this-children-visible
  417. next-has-some-visible-child)
  418. (insert " folded=\"true\"")))
  419. (when (and (= current-level (1+ base-level))
  420. (> num-left-nodes 0))
  421. (setq num-left-nodes (1- num-left-nodes))
  422. (insert " position=\"left\""))
  423. (when this-bg-color
  424. (insert " background_color=\"" this-bg-color "\""))
  425. (insert ">\n")
  426. (when this-icons
  427. (dolist (icon this-icons)
  428. (insert "<icon builtin=\"" icon "\"/>\n")))
  429. )
  430. (with-current-buffer mm-buffer
  431. (when this-rich-note (insert this-rich-note))
  432. (when this-rich-node (insert this-rich-node))
  433. )
  434. ))
  435. (defun org-freemind-check-overwrite (file interactively)
  436. (if (file-exists-p file)
  437. (if interactively
  438. (y-or-n-p (format "File %s exists, replace it? " file))
  439. (error "File %s already exists" file))
  440. t))
  441. (defvar org-freemind-node-pattern (rx bol
  442. (submatch (1+ "*"))
  443. (1+ space)
  444. (submatch (*? nonl))
  445. eol))
  446. (defun org-freemind-look-for-visible-child (node-level)
  447. (save-excursion
  448. (save-match-data
  449. (let ((found-visible-child nil))
  450. (while (and (not found-visible-child)
  451. (re-search-forward org-freemind-node-pattern nil t))
  452. (let* ((m1 (match-string-no-properties 1))
  453. (level (length m1)))
  454. (if (>= node-level level)
  455. (setq found-visible-child 'none)
  456. (unless (get-char-property (line-beginning-position) 'invisible)
  457. (setq found-visible-child 'found)))))
  458. (eq found-visible-child 'found)
  459. ))))
  460. (defun org-freemind-goto-line (line)
  461. (save-restriction
  462. (widen)
  463. (goto-char (point-min))
  464. (forward-line (1- line))))
  465. (defun org-freemind-write-mm-buffer (org-buffer mm-buffer node-at-line)
  466. (with-current-buffer org-buffer
  467. (dolist (node-style org-freemind-node-styles)
  468. (when (string-match-p (car node-style) buffer-file-name)
  469. (setq org-freemind-node-style (cadr node-style))))
  470. ;;(message "org-freemind-node-style =%s" org-freemind-node-style)
  471. (save-match-data
  472. (let* ((drawers (copy-sequence org-drawers))
  473. drawers-regexp
  474. (num-top1-nodes 0)
  475. (num-top2-nodes 0)
  476. num-left-nodes
  477. (unclosed-nodes 0)
  478. (first-time t)
  479. (current-level 1)
  480. base-level
  481. skipping-odd
  482. (skipped-odd 0)
  483. prev-node-end
  484. rich-text
  485. unfinished-tag
  486. node-at-line-level
  487. node-at-line-last)
  488. (with-current-buffer mm-buffer
  489. (erase-buffer)
  490. (insert "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
  491. (insert "<map version=\"0.9.0\">\n")
  492. (insert "<!-- To view this file, download free mind mapping software FreeMind from http://freemind.sourceforge.net -->\n"))
  493. (save-excursion
  494. ;; Get special buffer vars:
  495. (goto-char (point-min))
  496. (while (re-search-forward (rx bol "#+DRAWERS:") nil t)
  497. (let ((dr-txt (buffer-substring-no-properties (match-end 0) (line-end-position))))
  498. (setq drawers (append drawers (split-string dr-txt) nil))))
  499. (setq drawers-regexp
  500. (concat (rx bol (0+ blank) ":")
  501. (regexp-opt drawers)
  502. (rx ":" (0+ blank)
  503. "\n"
  504. (*? anything)
  505. "\n"
  506. (0+ blank)
  507. ":END:"
  508. (0+ blank)
  509. eol)
  510. ))
  511. (if node-at-line
  512. ;; Get number of top nodes and last line for this node
  513. (progn
  514. (org-freemind-goto-line node-at-line)
  515. (unless (looking-at org-freemind-node-pattern)
  516. (error "No node at line %s" node-at-line))
  517. (setq node-at-line-level (length (match-string-no-properties 1)))
  518. (forward-line)
  519. (setq node-at-line-last
  520. (catch 'last-line
  521. (while (re-search-forward org-freemind-node-pattern nil t)
  522. (let* ((m1 (match-string-no-properties 1))
  523. (level (length m1)))
  524. (if (<= level node-at-line-level)
  525. (progn
  526. (beginning-of-line)
  527. (throw 'last-line (1- (point))))
  528. (if (= level (1+ node-at-line-level))
  529. (setq num-top2-nodes (1+ num-top2-nodes))))))))
  530. (setq current-level node-at-line-level)
  531. (setq num-top1-nodes 1)
  532. (org-freemind-goto-line node-at-line))
  533. ;; First get number of top nodes
  534. (goto-char (point-min))
  535. (while (re-search-forward org-freemind-node-pattern nil t)
  536. (let* ((m1 (match-string-no-properties 1))
  537. (level (length m1)))
  538. (if (= level 1)
  539. (setq num-top1-nodes (1+ num-top1-nodes))
  540. (if (= level 2)
  541. (setq num-top2-nodes (1+ num-top2-nodes))))))
  542. ;; If there is more than one top node we need to insert a node
  543. ;; to keep them together.
  544. (goto-char (point-min))
  545. (when (> num-top1-nodes 1)
  546. (setq num-top2-nodes num-top1-nodes)
  547. (setq current-level 0)
  548. (let ((orig-name (if buffer-file-name
  549. (file-name-nondirectory (buffer-file-name))
  550. (buffer-name))))
  551. (with-current-buffer mm-buffer
  552. (insert "<node text=\"" orig-name "\" background_color=\"#00bfff\">\n"
  553. ;; Put a note that this is for the parent node
  554. "<richcontent TYPE=\"NOTE\"><html>"
  555. "<head>"
  556. "</head>"
  557. "<body>"
  558. "<p>"
  559. org-freemind-org-nfix "WHOLE FILE"
  560. "</p>"
  561. "</body>"
  562. "</html>"
  563. "</richcontent>\n")))))
  564. (setq num-left-nodes (floor num-top2-nodes 2))
  565. (setq base-level current-level)
  566. (let (this-m2
  567. this-node-end
  568. this-children-visible
  569. next-m2
  570. next-level
  571. next-has-some-visible-child
  572. next-children-visible
  573. )
  574. (while (and
  575. (re-search-forward org-freemind-node-pattern nil t)
  576. (if node-at-line-last (<= (point) node-at-line-last) t)
  577. )
  578. (let* ((next-m1 (match-string-no-properties 1))
  579. (next-node-start (match-beginning 0))
  580. (next-node-end (match-end 0))
  581. )
  582. (setq next-m2 (match-string-no-properties 2))
  583. (setq next-level (length next-m1))
  584. (when (> next-level current-level)
  585. (if (not (and org-odd-levels-only
  586. (/= (mod current-level 2) 0)
  587. (= next-level (+ 2 current-level))))
  588. (setq skipping-odd nil)
  589. (setq skipping-odd t)
  590. (setq skipped-odd (1+ skipped-odd)))
  591. (unless (or (= next-level (1+ current-level))
  592. skipping-odd)
  593. (error "Next level step > +1 for node ending at line %s" (line-number-at-pos))
  594. ))
  595. (setq next-children-visible
  596. (not (eq 'outline
  597. (get-char-property (line-end-position) 'invisible))))
  598. (setq next-has-some-visible-child
  599. (if next-children-visible t
  600. (org-freemind-look-for-visible-child next-level)))
  601. (when this-m2
  602. (org-freemind-write-node this-m2 this-node-end))
  603. (when (if (= num-top1-nodes 1) (> current-level base-level) t)
  604. (while (>= current-level next-level)
  605. (with-current-buffer mm-buffer
  606. (insert "</node>\n")
  607. ;;(insert (format "</node>\ncurrent-level=%s, next-level%s\n" current-level next-level))
  608. (setq current-level (1- current-level))
  609. (when (< 0 skipped-odd)
  610. (setq skipped-odd (1- skipped-odd))
  611. (setq current-level (1- current-level)))
  612. )))
  613. (setq this-node-end (1+ next-node-end))
  614. (setq this-m2 next-m2)
  615. (setq current-level next-level)
  616. (setq this-children-visible next-children-visible)
  617. (forward-char)
  618. ))
  619. ;;; (unless (if node-at-line-last
  620. ;;; (>= (point) node-at-line-last)
  621. ;;; nil)
  622. ;; Write last node:
  623. (setq this-m2 next-m2)
  624. (setq current-level next-level)
  625. (setq next-node-start (if node-at-line-last
  626. (1+ node-at-line-last)
  627. (point-max)))
  628. (org-freemind-write-node this-m2 this-node-end)
  629. (with-current-buffer mm-buffer (insert "</node>\n"))
  630. ;)
  631. )
  632. (with-current-buffer mm-buffer
  633. (while (> current-level base-level)
  634. (insert "</node>\n")
  635. (setq current-level (1- current-level))
  636. ))
  637. (with-current-buffer mm-buffer
  638. (insert "</map>")
  639. (delete-trailing-whitespace)
  640. (goto-char (point-min))
  641. ))))))
  642. (defun org-freemind-get-node-style (node-name)
  643. "NOT READY YET."
  644. ;;<node BACKGROUND_COLOR="#eeee00" CREATED="1234668815593" MODIFIED="1234668815593" STYLE="bubble">
  645. ;;<font BOLD="true" NAME="SansSerif" SIZE="12"/>
  646. (let (node-styles
  647. node-style)
  648. (dolist (style-list org-freemind-node-style)
  649. (let ((node-regexp (car style-list)))
  650. (message "node-regexp=%s node-name=%s" node-regexp node-name)
  651. (when (string-match-p node-regexp node-name)
  652. ;;(setq node-style (org-freemind-do-apply-node-style style-list))
  653. (setq node-style (cadr style-list))
  654. (when node-style
  655. (message "node-style=%s" node-style)
  656. (setq node-styles (append node-styles node-style)))
  657. )))))
  658. (defun org-freemind-do-apply-node-style (style-list)
  659. (message "style-list=%S" style-list)
  660. (let ((node-style 'fork)
  661. (color "red")
  662. (background-color "yellow")
  663. (edge-color "green")
  664. (edge-style 'bezier)
  665. (edge-width 'thin)
  666. (italic t)
  667. (bold t)
  668. (font-name "SansSerif")
  669. (font-size 12))
  670. (dolist (style (cadr style-list))
  671. (message " style=%s" style)
  672. (let ((what (car style)))
  673. (cond
  674. ((eq what 'node-style)
  675. (setq node-style (cadr style)))
  676. ((eq what 'color)
  677. (setq color (cadr style)))
  678. ((eq what 'background-color)
  679. (setq background-color (cadr style)))
  680. ((eq what 'edge-color)
  681. (setq edge-color (cadr style)))
  682. ((eq what 'edge-style)
  683. (setq edge-style (cadr style)))
  684. ((eq what 'edge-width)
  685. (setq edge-width (cadr style)))
  686. ((eq what 'italic)
  687. (setq italic (cadr style)))
  688. ((eq what 'bold)
  689. (setq bold (cadr style)))
  690. ((eq what 'font-name)
  691. (setq font-name (cadr style)))
  692. ((eq what 'font-size)
  693. (setq font-size (cadr style)))
  694. )
  695. (insert (format " style=\"%s\"" node-style))
  696. (insert (format " color=\"%s\"" color))
  697. (insert (format " background_color=\"%s\"" background-color))
  698. (insert ">\n")
  699. (insert "<edge")
  700. (insert (format " color=\"%s\"" edge-color))
  701. (insert (format " style=\"%s\"" edge-style))
  702. (insert (format " width=\"%s\"" edge-width))
  703. (insert "/>\n")
  704. (insert "<font")
  705. (insert (format " italic=\"%s\"" italic))
  706. (insert (format " bold=\"%s\"" bold))
  707. (insert (format " name=\"%s\"" font-name))
  708. (insert (format " size=\"%s\"" font-size))
  709. ))))
  710. ;;;###autoload
  711. (defun org-freemind-from-org-mode-node (node-line mm-file)
  712. "Convert node at line NODE-LINE to the FreeMind file MM-FILE."
  713. (interactive
  714. (progn
  715. (unless (org-back-to-heading nil)
  716. (error "Can't find org-mode node start"))
  717. (let* ((line (line-number-at-pos))
  718. (default-mm-file (concat (if buffer-file-name
  719. (file-name-nondirectory buffer-file-name)
  720. "nofile")
  721. "-line-" (number-to-string line)
  722. ".mm"))
  723. (mm-file (read-file-name "Output FreeMind file: " nil nil nil default-mm-file)))
  724. (list line mm-file))))
  725. (when (org-freemind-check-overwrite mm-file (called-interactively-p))
  726. (let ((org-buffer (current-buffer))
  727. (mm-buffer (find-file-noselect mm-file)))
  728. (org-freemind-write-mm-buffer org-buffer mm-buffer node-line)
  729. (with-current-buffer mm-buffer
  730. (basic-save-buffer)
  731. (when (called-interactively-p)
  732. (switch-to-buffer-other-window mm-buffer)
  733. (when (y-or-n-p "Show in FreeMind? ")
  734. (org-freemind-show buffer-file-name)))))))
  735. ;;;###autoload
  736. (defun org-freemind-from-org-mode (org-file mm-file)
  737. "Convert the `org-mode' file ORG-FILE to the FreeMind file MM-FILE."
  738. ;; Fix-me: better doc, include recommendations etc.
  739. (interactive
  740. (let* ((org-file buffer-file-name)
  741. (default-mm-file (concat
  742. (if org-file
  743. (file-name-nondirectory org-file)
  744. "nofile")
  745. ".mm"))
  746. (mm-file (read-file-name "Output FreeMind file: " nil nil nil default-mm-file)))
  747. (list org-file mm-file)))
  748. (when (org-freemind-check-overwrite mm-file (called-interactively-p))
  749. (let ((org-buffer (if org-file (find-file-noselect org-file) (current-buffer)))
  750. (mm-buffer (find-file-noselect mm-file)))
  751. (org-freemind-write-mm-buffer org-buffer mm-buffer nil)
  752. (with-current-buffer mm-buffer
  753. (basic-save-buffer)
  754. (when (called-interactively-p)
  755. (switch-to-buffer-other-window mm-buffer)
  756. (when (y-or-n-p "Show in FreeMind? ")
  757. (org-freemind-show buffer-file-name)))))))
  758. ;;;###autoload
  759. (defun org-freemind-from-org-sparse-tree (org-buffer mm-file)
  760. "Convert visible part of buffer ORG-BUFFER to FreeMind file MM-FILE."
  761. (interactive
  762. (let* ((org-file buffer-file-name)
  763. (default-mm-file (concat
  764. (if org-file
  765. (file-name-nondirectory org-file)
  766. "nofile")
  767. "-sparse.mm"))
  768. (mm-file (read-file-name "Output FreeMind file: " nil nil nil default-mm-file)))
  769. (list (current-buffer) mm-file)))
  770. (when (org-freemind-check-overwrite mm-file (called-interactively-p))
  771. (let (org-buffer
  772. (mm-buffer (find-file-noselect mm-file)))
  773. (save-window-excursion
  774. (org-export-visible ?\ nil)
  775. (setq org-buffer (current-buffer)))
  776. (org-freemind-write-mm-buffer org-buffer mm-buffer nil)
  777. (with-current-buffer mm-buffer
  778. (basic-save-buffer)
  779. (when (called-interactively-p)
  780. (switch-to-buffer-other-window mm-buffer)
  781. (when (y-or-n-p "Show in FreeMind? ")
  782. (org-freemind-show buffer-file-name)))))))
  783. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  784. ;;; FreeMind => Org
  785. ;; (sort '(b a c) 'org-freemind-lt-symbols)
  786. (defun org-freemind-lt-symbols (sym-a sym-b)
  787. (string< (symbol-name sym-a) (symbol-name sym-b)))
  788. ;; (sort '((b . 1) (a . 2) (c . 3)) 'org-freemind-lt-xml-attrs)
  789. (defun org-freemind-lt-xml-attrs (attr-a attr-b)
  790. (string< (symbol-name (car attr-a)) (symbol-name (car attr-b))))
  791. ;; xml-parse-region gives things like
  792. ;; ((p nil "\n"
  793. ;; (a
  794. ;; ((href . "link"))
  795. ;; "text")
  796. ;; "\n"
  797. ;; (b nil "hej")
  798. ;; "\n"))
  799. ;; '(a . nil)
  800. ;; (org-freemind-symbols= 'a (car '(A B)))
  801. (defsubst org-freemind-symbols= (sym-a sym-b)
  802. (or (eq sym-a sym-b)
  803. (string= (downcase (symbol-name sym-a))
  804. (downcase (symbol-name sym-b)))))
  805. (defun org-freemind-get-children (parent path)
  806. "Find children node to PARENT from PATH.
  807. PATH should be a list of steps, where each step has the form
  808. '(NODE-NAME (ATTR-NAME . ATTR-VALUE))
  809. "
  810. ;; Fix-me: maybe implement op? step: Name, number, attr, attr op val
  811. ;; Fix-me: case insensitive version for children?
  812. (let* ((children (if (not (listp (car parent)))
  813. (cddr parent)
  814. (let (cs)
  815. (dolist (p parent)
  816. (dolist (c (cddr p))
  817. (add-to-list 'cs c)))
  818. cs)
  819. ))
  820. (step (car path))
  821. (step-node (if (listp step) (car step) step))
  822. (step-attr-list (when (listp step) (sort (cdr step) 'org-freemind-lt-xml-attrs)))
  823. (path-tail (cdr path))
  824. path-children)
  825. (dolist (child children)
  826. ;; skip xml.el formatting nodes
  827. (unless (stringp child)
  828. ;; compare node name
  829. (when (if (not step-node)
  830. t ;; any node name
  831. (org-freemind-symbols= step-node (car child)))
  832. (if (not step-attr-list)
  833. ;;(throw 'path-child child) ;; no attr to care about
  834. (add-to-list 'path-children child)
  835. (let* ((child-attr-list (cadr child))
  836. (step-attr-copy (copy-sequence step-attr-list)))
  837. (dolist (child-attr child-attr-list)
  838. ;; Compare attr names:
  839. (when (org-freemind-symbols= (caar step-attr-copy) (car child-attr))
  840. ;; Compare values:
  841. (let ((step-val (cdar step-attr-copy))
  842. (child-val (cdr child-attr)))
  843. (when (if (not step-val)
  844. t ;; any value
  845. (string= step-val child-val))
  846. (setq step-attr-copy (cdr step-attr-copy))))))
  847. ;; Did we find all?
  848. (unless step-attr-copy
  849. ;;(throw 'path-child child)
  850. (add-to-list 'path-children child)
  851. ))))))
  852. (if path-tail
  853. (org-freemind-get-children path-children path-tail)
  854. path-children)))
  855. (defun org-freemind-get-richcontent-node (node)
  856. (let ((rc-nodes
  857. (org-freemind-get-children node '((richcontent (type . "NODE")) html body))))
  858. (when (> (length rc-nodes) 1)
  859. (lwarn t :warning "Unexpected structure: several <richcontent type=\"NODE\" ...>"))
  860. (car rc-nodes)))
  861. (defun org-freemind-get-richcontent-note (node)
  862. (let ((rc-notes
  863. (org-freemind-get-children node '((richcontent (type . "NOTE")) html body))))
  864. (when (> (length rc-notes) 1)
  865. (lwarn t :warning "Unexpected structure: several <richcontent type=\"NOTE\" ...>"))
  866. (car rc-notes)))
  867. (defun org-freemind-test-get-tree-text ()
  868. (let ((node '(p nil "\n"
  869. (a
  870. ((href . "link"))
  871. "text")
  872. "\n"
  873. (b nil "hej")
  874. "\n")))
  875. (org-freemind-get-tree-text node)))
  876. ;; (org-freemind-test-get-tree-text)
  877. (defun org-freemind-get-tree-text (node)
  878. (when node
  879. (let ((ntxt "")
  880. (link nil)
  881. (lf-after nil))
  882. (dolist (n node)
  883. (case n
  884. ;;(a (setq is-link t) )
  885. ((h1 h2 h3 h4 h5 h6 p)
  886. ;;(setq ntxt (concat "\n" ntxt))
  887. (setq lf-after 2)
  888. )
  889. (br
  890. (setq lf-after 1)
  891. )
  892. (t
  893. (cond
  894. ((stringp n)
  895. (when (string= n "\n") (setq n ""))
  896. (if link
  897. (setq ntxt (concat ntxt
  898. "[[" link "][" n "]]"))
  899. (setq ntxt (concat ntxt n))))
  900. ((and n (listp n))
  901. (if (symbolp (car n))
  902. (setq ntxt (concat ntxt (org-freemind-get-tree-text n)))
  903. ;; This should be the attributes:
  904. (dolist (att-val n)
  905. (let ((att (car att-val))
  906. (val (cdr att-val)))
  907. (when (eq att 'href)
  908. (setq link val)))))
  909. )))))
  910. (if lf-after
  911. (setq ntxt (concat ntxt (make-string lf-after ?\n)))
  912. (setq ntxt (concat ntxt " ")))
  913. ;;(setq ntxt (concat ntxt (format "{%s}" n)))
  914. ntxt)))
  915. (defun org-freemind-get-richcontent-node-text (node)
  916. "Get the node text as from the richcontent note NODE."
  917. (save-match-data
  918. (let* ((rc (org-freemind-get-richcontent-node node))
  919. (txt (org-freemind-get-tree-text rc)))
  920. ;;(when txt (setq txt (replace-regexp-in-string (rx (1+ whitespace)) " " txt)))
  921. txt
  922. )))
  923. (defun org-freemind-get-richcontent-note-text (node)
  924. "Get the node text as from the richcontent note NODE."
  925. (save-match-data
  926. (let* ((rc (org-freemind-get-richcontent-note node))
  927. (txt (when rc (org-freemind-get-tree-text rc))))
  928. ;;(when txt (setq txt (replace-regexp-in-string (rx (1+ whitespace)) " " txt)))
  929. txt
  930. )))
  931. (defun org-freemind-get-icon-names (node)
  932. (let* ((icon-nodes (org-freemind-get-children node '((icon ))))
  933. names)
  934. (dolist (icn icon-nodes)
  935. (setq names (cons (cdr (assq 'builtin (cadr icn))) names)))
  936. ;; (icon (builtin . "full-1"))
  937. names))
  938. (defun org-freemind-node-to-org (node level skip-levels)
  939. (let ((qname (car node))
  940. (attributes (cadr node))
  941. text
  942. (note (org-freemind-get-richcontent-note-text node))
  943. (mark "-- This is more about ")
  944. (icons (org-freemind-get-icon-names node))
  945. (children (cddr node)))
  946. (when (< 0 (- level skip-levels))
  947. (dolist (attrib attributes)
  948. (case (car attrib)
  949. ('TEXT (setq text (cdr attrib)))
  950. ('text (setq text (cdr attrib)))))
  951. (unless text
  952. ;; There should be a richcontent node holding the text:
  953. (setq text (org-freemind-get-richcontent-node-text node)))
  954. (when icons
  955. (when (member "full-1" icons) (setq text (concat "[#A] " text)))
  956. (when (member "full-2" icons) (setq text (concat "[#B] " text)))
  957. (when (member "full-3" icons) (setq text (concat "[#C] " text)))
  958. (when (member "full-4" icons) (setq text (concat "[#D] " text)))
  959. (when (member "full-5" icons) (setq text (concat "[#E] " text)))
  960. (when (member "full-6" icons) (setq text (concat "[#F] " text)))
  961. (when (member "full-7" icons) (setq text (concat "[#G] " text)))
  962. (when (member "button_cancel" icons) (setq text (concat "TODO " text)))
  963. )
  964. (if (and note
  965. (string= mark (substring note 0 (length mark))))
  966. (progn
  967. (setq text (replace-regexp-in-string "\n $" "" text))
  968. (insert text))
  969. (case qname
  970. ('node
  971. (insert (make-string (- level skip-levels) ?*) " " text "\n")
  972. ))))
  973. (dolist (child children)
  974. (unless (or (null child)
  975. (stringp child))
  976. (org-freemind-node-to-org child (1+ level) skip-levels)))))
  977. ;; Fix-me: put back special things, like drawers that are stored in
  978. ;; the notes. Should maybe all notes contents be put in drawers?
  979. ;;;###autoload
  980. (defun org-freemind-to-org-mode (mm-file org-file)
  981. "Convert FreeMind file MM-FILE to `org-mode' file ORG-FILE."
  982. (interactive
  983. (save-match-data
  984. (let* ((mm-file (buffer-file-name))
  985. (default-org-file (concat (file-name-nondirectory mm-file) ".org"))
  986. (org-file (read-file-name "Output org-mode file: " nil nil nil default-org-file)))
  987. (list mm-file org-file))))
  988. (when (org-freemind-check-overwrite org-file (called-interactively-p))
  989. (let ((mm-buffer (find-file-noselect mm-file))
  990. (org-buffer (find-file-noselect org-file)))
  991. (with-current-buffer mm-buffer
  992. (let* ((xml-list (xml-parse-file mm-file))
  993. (top-node (cadr (cddar xml-list)))
  994. (note (org-freemind-get-richcontent-note-text top-node))
  995. (skip-levels
  996. (if (and note
  997. (string-match (rx bol "--org-mode: WHOLE FILE" eol) note))
  998. 1
  999. 0)))
  1000. (with-current-buffer org-buffer
  1001. (erase-buffer)
  1002. (org-freemind-node-to-org top-node 1 skip-levels)
  1003. (goto-char (point-min))
  1004. (org-set-tags t t) ;; Align all tags
  1005. )
  1006. (switch-to-buffer-other-window org-buffer)
  1007. )))))
  1008. (provide 'org-freemind)
  1009. ;; arch-tag: e7b0d776-94fd-404a-b35e-0f855fae3627
  1010. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  1011. ;;; org-freemind.el ends here