Browse Source

Add Freemind Mindmap Back-End for Org Export Engine

* contrib/lisp/ox-freemind.el: New file.

* lisp/ox-html.el (org-html--tags, org-html-format-headline)
(org-html--format-toc-headline, org-html-checkbox)
(org-html-table-cell, org-html-timestamp)
(org-html-verse-block, org-html-special-string-regexps):
Replace named HTML entities with their numeric counterparts.
This keeps Freemind backend happy.
Jambunathan K 12 years ago
parent
commit
f2b2c8318f
2 changed files with 554 additions and 11 deletions
  1. 543 0
      contrib/lisp/ox-freemind.el
  2. 11 11
      lisp/ox-html.el

+ 543 - 0
contrib/lisp/ox-freemind.el

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

+ 11 - 11
lisp/ox-html.el

@@ -137,10 +137,10 @@
 (defvar htmlize-buffer-places)  ; from htmlize.el
 
 (defconst org-html-special-string-regexps
-  '(("\\\\-" . "&shy;")
-    ("---\\([^-]\\)" . "&mdash;\\1")
-    ("--\\([^-]\\)" . "&ndash;\\1")
-    ("\\.\\.\\." . "&hellip;"))
+  '(("\\\\-" . "&#x00ad;")		; shy
+    ("---\\([^-]\\)" . "&#x2014;\\1")	; mdash
+    ("--\\([^-]\\)" . "&#x2013;\\1")	; ndash
+    ("\\.\\.\\." . "&#x2026;"))		; hellip
   "Regular expressions for special string conversion.")
 
 (defconst org-html-scripts
@@ -1580,7 +1580,7 @@ INFO is a plist used as a communication channel."
 		       (concat org-html-tag-class-prefix
 			       (org-html-fix-class-name tag))
 		       tag))
-	     tags "&nbsp;"))))
+	     tags "&#xa0;"))))
 
 ;;;; Headline
 
@@ -1594,7 +1594,7 @@ INFO is a plist used as a communication channel."
 	(todo (org-html--todo todo))
 	(tags (org-html--tags tags)))
     (concat section-number todo (and todo " ") text
-	    (and tags "&nbsp;&nbsp;&nbsp;") tags)))
+	    (and tags "&#xa0;&#xa0;&#xa0;") tags)))
 
 ;;;; Src Code
 
@@ -1781,7 +1781,7 @@ INFO is a plist used as a communication channel."
 			(target . ignore))
 		      (org-export-backend-translate-table 'html))
 		     info)
-		    (and tags "&nbsp;&nbsp;&nbsp;") (org-html--tags tags)))))
+		    (and tags "&#xa0;&#xa0;&#xa0;") (org-html--tags tags)))))
 
 (defun org-html-list-of-listings (info)
   "Build a list of listings.
@@ -2185,7 +2185,7 @@ contextual information."
 
 (defun org-html-checkbox (checkbox)
   (case checkbox (on "<code>[X]</code>")
-	(off "<code>[&nbsp;]</code>")
+	(off "<code>[&#xa0;]</code>")
 	(trans "<code>[-]</code>")
 	(t "")))
 
@@ -2857,7 +2857,7 @@ channel."
 			" align=\"%s\"" " class=\"%s\"")
 		    (org-export-table-cell-alignment table-cell info)))))
     (when (or (not contents) (string= "" (org-trim contents)))
-      (setq contents "&nbsp;"))
+      (setq contents "&#xa0;"))
     (cond
      ((and (org-export-table-has-header-p table info)
 	   (= 1 (org-export-table-row-group table-row info)))
@@ -3010,7 +3010,7 @@ information."
   (let ((value (org-html-plain-text
 		(org-timestamp-translate timestamp) info)))
     (format "<span class=\"timestamp-wrapper\"><span class=\"timestamp\">%s</span></span>"
-	    (replace-regexp-in-string "--" "&ndash;" value))))
+	    (replace-regexp-in-string "--" "&#x2013;" value))))
 
 
 ;;;; Underline
@@ -3050,7 +3050,7 @@ contextual information."
   (while (string-match "^[ \t]+" contents)
     (let* ((num-ws (length (match-string 0 contents)))
 	   (ws (let (out) (dotimes (i num-ws out)
-			    (setq out (concat out "&nbsp;"))))))
+			    (setq out (concat out "&#xa0;"))))))
       (setq contents (replace-match ws nil t contents))))
   (format "<p class=\"verse\">\n%s</p>" contents))