Emacs: Emacs Markdown to HTML 📜

By Xah Lee. Date: . Last updated: .

this is a quick hack

put this in your Emacs Init File:

(defun xah-html-markdown-to-html (&optional Begin End)
  "Replace markdown text to HTML in current block or selection.

**bold**, *italics*
# heading 1
## heading 2
### heading 3
#### heading 4

* list

currently not support:
lowline for bold, italic (use asterisk)
blockquote
ordered list
link
image

URL `http://xahlee.info/emacs/emacs/emacs_markdown_to_html.html'

Created: 2023-11-10
Version: 2025-12-10"
  (interactive)
  (let (xbeg xend)
    (seq-setq (xbeg xend)
              (if (and Begin End)
                  (list Begin End)
                (if (region-active-p)
                    (list (region-beginning) (region-end))
                  (if (region-active-p) (list (region-beginning) (region-end)) (list (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point))) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))))))
    (save-excursion
      (save-restriction
        (narrow-to-region xbeg xend)

        (progn
          ;; insert 2 eol char at begining and end buffer ;; so easier to process ;; remove them after done
          (goto-char (point-min))
          (insert "\n\n")
          (goto-char (point-max))
          (insert "\n\n"))

        (goto-char (point-min))
        (xah-html-encode-ampersand-entity (point-min) (point-max))

        (goto-char (point-min))
        (while (re-search-forward ":\n-" nil t)
          (replace-match ":\n\n-" t t))

        (goto-char (point-min))
        (while (re-search-forward "^#.+$" nil t)
          (goto-char (pos-eol))
          (insert "\n"))

        (goto-char (point-min))
        (while (re-search-forward "^# \\(.+\\)$" nil t)
          (replace-match "<h1>\\1</h1>" t))

        (goto-char (point-min))
        (while (re-search-forward "^## \\(.+\\)$" nil t)
          (replace-match "<h2>\\1</h2>" t))

        (goto-char (point-min))
        (while (re-search-forward "^### \\(.+\\)$" nil t)
          (replace-match "<h3>\\1</h3>" t))

        (goto-char (point-min))
        (while (re-search-forward "^#### \\(.+\\)$" nil t)
          (replace-match "<h4>\\1</h4>" t))

        (goto-char (point-min))
        (while (re-search-forward "^##### \\(.+\\)$" nil t)
          (replace-match "<h5>\\1</h5>" t))

        (goto-char (point-min))
        (while (re-search-forward "^###### \\(.+\\)$" nil t)
          (replace-match "<h6>\\1</h6>" t))

        (goto-char (point-min))
        (while (re-search-forward "\n````\\([^`]+\\)````\n" nil t)
          (replace-match "\n<pre>\\1</pre>\n" t))

        (goto-char (point-min))
        (while (re-search-forward "\n```\\([^`]+\\)```\n" nil t)
          (replace-match "\n<pre>\\1</pre>\n" t))

        (goto-char (point-min))
        (while (re-search-forward "``\\([^`]+\\)``" nil t)
          (replace-match "<code>\\1</code>" t))

        (goto-char (point-min))
        (while (re-search-forward "`\\([^`]+\\)`" nil t)
          (replace-match "<code>\\1</code>" t))

        (progn
          (goto-char (point-min))
          (while (re-search-forward "^\\([*-] \\)\\(.+\\)$" nil t)
            (replace-match "<li>\\2</li>" t))

          (goto-char (point-min))
          (while (re-search-forward "\n\n<li>" nil t)
            (replace-match "\n\n<ul>\n<li>" t))

          (goto-char (point-min))
          (while (re-search-forward "</li>\n\n" nil t)
            (replace-match "</li>\n</ul>\n\n" t)))

        ;; ordered list
        ;; (progn
        ;;           (goto-char (point-min))
        ;;           (while (re-search-forward "
        ;; 1\\. \\(.+\\)
        ;; 2\\. \\(.+\\)
        ;; " nil t)
        ;;             (replace-match "<li>\\1</li>" t)))

        (goto-char (point-min))
        (while (re-search-forward "\\*\\*\\([^*]+\\)\\*\\*" nil t)
          (replace-match "<b>\\1</b>" t))

        (goto-char (point-min))
        (while (re-search-forward "\\*\\([- ()A-Za-z0-9]+\\)\\*" nil t)
          (replace-match "<i>\\1</i>" t))

        (goto-char (point-min))
        (while (re-search-forward "\n\n\\([^<\n].+[^>\n]\\)\n\n" nil t)
          (replace-match "\n\n<p>\\1</p>\n\n" t)
          (backward-char 2))

        (progn
          (goto-char (point-min))
          (delete-char 2)
          (goto-char (point-max))
          (delete-char -2))
        ;;
        ))))

markdown