Using org2blog to publish Org-mode subtrees

This patch modifies punchagan’s org2blog to allow you to publish an Org subtree with M-x org2blog-post-subtree. It posts a draft by default, and publishes the post if you call it with C-u M-x org2blog-post-subtree. It gets the posting date from SCHEDULED, DEADLINE, active or inactive timestamps, or the Post Date property, and lets you use tags as categories or use a separate Categories property. It inherits tags from parent headings, too. It picks up the title from the subtree heading or uses the Title property.


diff --git a/org2blog.el b/org2blog.el
index dc88291..b95caba 100644
--- a/org2blog.el
+++ b/org2blog.el
@@ -77,6 +77,11 @@
   :group 'org2blog 
   :type 'string)
+(defcustom org2blog-use-tags-as-categories nil
+  "Non-nil means assign :tags: to WordPress categories instead."
+  :group 'org2blog
+  :type 'boolean)
 (defvar org2blog-categories-list nil 
   "List of weblog categories")
@@ -433,4 +438,143 @@
        (goto-char current-pos)
        (command-execute (lookup-key org-mode-map (kbd "C-c t")))))))
+(defun org2blog-create-categories (categories)
+  "Create unknown CATEGORIES."
+  (mapcar
+   (lambda (cat)
+     (if (and (not (member cat org2blog-categories-list))
+              (y-or-n-p (format "Create %s category? " cat)))
+         (wp-new-category org2blog-server-xmlrpc-url
+                          org2blog-server-userid
+                          (org2blog-password)
+                          org2blog-server-blogid
+                          cat)))
+   categories))
+(defun org2blog-password ()
+  "Get password or prompt if needed."
+  (or org2blog-server-pass
+      (setq org2blog-server-pass (read-passwd "Weblog password? "))))
+(defun org2blog-upload-images-insert-links (&optional beg end)
+  "Upload images and replace with links in the region specified by BEG to END."
+  (interactive "r")
+  (let ((re 
+        (concat "\\[\\[\\(.*\\)" 
+                (substring (org-image-file-name-regexp) 0 -2)
+                "\\]\\]"))
+       file-all-urls file-name file-web-url blog-pass)
+    (save-excursion
+      (save-restriction
+        (narrow-to-region (or beg (point-min))
+                          (or end (point-max)))
+        (goto-char (point-min))
+        (while (re-search-forward re nil t 1)
+          (setq file-name (concat 
+                           (match-string-no-properties 1)
+                           "."
+                           (match-string-no-properties 2)))
+          (unless (save-match-data (string-match org-link-types-re file-name))
+            (save-match-data 
+              (if (assoc file-name file-all-urls)
+                  (setq file-web-url (cdr (assoc file-name file-all-urls)))
+                (setq file-web-url
+                      (cdr (assoc "url" 
+                                  (metaweblog-upload-image org2blog-server-xmlrpc-url
+                                                           org2blog-server-userid
+                                                           (org2blog-password)
+                                                           org2blog-server-weblog-id
+                                                           (get-image-properties file-name))))
+                      file-all-urls (append file-all-urls (list (cons 
+                                                                 file-name file-web-url))))))
+            (replace-match (concat "[[" file-web-url "]]") t t nil 0)))))
+    file-all-urls))
+(defun org2blog-post-subtree (&optional publish)
+  "Post the current entry as a draft. Publish if PUBLISH is non-nil."
+  (interactive "P")
+  (let ((post (org2blog-parse-subtree))
+        post-id)
+    (org2blog-create-categories (cdr (assoc "categories" post)))
+    (setq post-id (cdr (assoc "post-id" post)))
+    (save-excursion 
+      (org2blog-upload-images-insert-links (org-back-to-heading) (org-end-of-subtree)))
+    (if post-id
+        (metaweblog-edit-post org2blog-server-xmlrpc-url
+                             org2blog-server-userid
+                              (org2blog-password)
+                             post-id
+                              post
+                             publish)
+      (setq post-id
+            (metaweblog-new-post
+             org2blog-server-xmlrpc-url
+             org2blog-server-userid
+             (org2blog-password)
+             org2blog-server-blogid
+             post
+             publish))
+      (org-entry-put (point) "Post ID" post-id)
+      (message (if publish
+                   "Published (%s): %s"
+                 "Draft (%s): %s")
+               post-id
+               (cdr (assoc "title" post))))))
+(defun org2blog-parse-subtree ()
+  "Parse the current subtree as a blog entry."
+  (let (html-text
+        (post-title (or (org-entry-get (point) "Title")
+                        (org-get-heading t)))
+        (post-id (org-entry-get (point) "Post ID"))
+        ;; Set post-date to the Post Date property or look for timestamp
+        (post-date (or (org-entry-get (point) "Post Date")
+                       (org-entry-get (point) "SCHEDULED")
+                       (org-entry-get (point) "DEADLINE")                       
+                       (org-entry-get (point) "TIMESTAMP_IA")
+                       (org-entry-get (point) "TIMESTAMP")))
+        (tags (org-get-tags-at (point) nil))
+        (categories (org-split-string (or (org-entry-get (point) "CATEGORIES") "") ":")))
+    ;; Convert post date to ISO timestamp
+    (setq post-date
+          (format-time-string "%Y%m%dT%T"
+                              (if post-date
+                                  (apply 'encode-time (org-parse-time-string post-date))
+                                (current-time))
+                              t))
+    (if org2blog-use-tags-as-categories
+        (setq categories tags
+              tags nil))
+    (save-excursion
+      (setq html-text
+            (org-export-region-as-html
+             (and (org-back-to-heading) (line-end-position))
+             (org-end-of-subtree)
+             t 'string))
+      (setq html-text
+            (with-temp-buffer
+              (insert html-text)
+              (goto-char (point-min))
+              ;; Fix newlines
+             (let (start-pos end-pos)
+                (setq start-pos (point-min))
+               (goto-char start-pos)
+                (while (re-search-forward "<\\(pre\\|blockquote\\).*?>" nil t 1)
+                  (setq end-pos (match-beginning 0))
+                  (replace-string "\n" " " nil start-pos end-pos)
+                  (re-search-forward (concat "</" (match-string-no-properties 1) ">") nil t 1)
+                  (setq start-pos (match-end 0))
+                  (goto-char start-pos))
+               (setq end-pos (point-max))
+               (replace-string "\n" " " nil start-pos end-pos))
+              ;; Copy the text
+              (buffer-substring-no-properties (point-min) (point-max)))))
+    (list
+     (cons "date" post-date)
+     (cons "title" post-title)
+     (cons "tags" tags)
+     (cons "categories" categories)
+     (cons "post-id" post-id)
+     (cons "description" html-text))))
 (provide 'org2blog)

I like using one big Org file for all of my notes so that I can search and categorize things easily.

Here is the sample code from my ~/.emacs:

(add-to-list 'load-path "~/elisp/org2blog")
(require 'org2blog)
(setq org2blog-server-url ""
      org2blog-server-user "admin"
      org2blog-server-weblog-id ""
      org2blog-use-tags-as-categories t)

Then I can go to the entry and call M-x org2blog-post-subtree to post a draft or C-u M-x org2blog-post-subtree to publish it.

Note that the code uses whatever heading level you’re on, so if you’re under a sub-heading of the post you want to publish, use C-c C-u outline-up-heading to go up headings until you’re at the right level.

You can get the modified source code from . I’ve also sent a pull request upstream.

5 Pingbacks/Trackbacks

  • Luiz Gavioli

    I’ve tried to use org2blog to post an article to my blog but I’m behind a proxy! Is there a way to use org2blog through proxies?

  • Sacha Chua

    org2blog uses xml-rpc, which uses url, so you should be able to customize url-proxy-services. Hope that helps!

  • Sacha Chua

    punchagan accepted the patch upstream, and someone else contributed bugfixes. Pull from the main repository instead! =)

  • punchagan

    I am behind a proxy too! The whole development of org2blog was done behind a proxy. :-)

    Thanks for the patch.

    • Sacha Chua

      I don’t know if I’ve got the times right. When I publish my subtree, the posts seem to have the right time in WordPress, but they stay Scheduled instead of Published. Editing the post and fiddling with the time does the right thing, though. Hmm…

  • punchagan

    It’s a problem with the timezone of your blog and the machine you post from.

    I use the format-time-string call without passing the UNIVERSAL argument and it works fine.
    When the UNIVERSAL argument is set, my posts are shown to be edited 6 hours ago, which is the time I’m ahead of UTC.

    Hope that helps.

  • Luiz Gavioli

    Thank you for the help! url-proxy-services really worked!

  • Pingback: – blog» Blog Archive » Org2blog, enviando apenas um sub-tópico.()

  • flaviosouza

    sacha / punchagan

    I have the same problem with time, how do I fix it with format-time-string ?

    help me….

  • Sacha Chua

    flaviosouza: Try updating, the latest version should be okay. Or remove the t argument from format-time-string, I think?

  • flaviosouza


    I found out format-time-string in the org2blog.el. It is working now.

    I did a quick post in my blog. This is the google translated version:

  • Pingback: Monthly review: July 2010 » sacha chua :: living an awesome life()

  • vedang

    Thanks! this looks great, I’ll be sure to try it over the weekend!

  • Pingback: New note-taking workflow with Emacs Org-mode » sacha chua :: living an awesome life()

  • Pingback: emacs, org-mode und wordpress «

  • Pingback: Setup org-2-blog for my installation | btd67()

  • wohfab

    Hey Sacha!

    Did you ever have a no methodresponse found error when doing a org2blog/wp-post-subtree? I got one, every time there is a link in my post.. ( ).. They couldn’t help me, yet; And I couldn’t help myself either.. Maybe you have an idea?