If you enjoyed this site, please consider donating $3. Any amount is appreciated. Thanks!

How To Update Web Feed With Elisp

Xah Lee, 2009-01-21, 2010-03-05

This page shows a example of writing a emacs lisp command that updates a web feed file (Atom/RSS). If you don't know elisp, first take a look at Emacs Lisp Basics.

The Problem

Summary

I want to write a command, so that, when invoked, the current selected text will be added as a entry in a Atom webfeed file.

This lesson will show you how write a command that grabs the region text, switch buffer, search string to locate position for inserting text, insert the text, and update date field in a file.

Detail

I run several blogs on my personal website. For example, blog on Emacs, Programing, Web Dev, Math. Each of these has a web feed in Atom format.

Let's take the emacs blog for example. The file is blog.html. Typically, i open that file, write there, then save. The file sits on my local disk, and is periodically synced to my web server. For each of the blog file, there's also a corresponding web feed, so that readers can subscribe to it.

To create a web feed, i've choosen the Atom format. Basically, it is a xml file with tags for blog entries. (See: Atom Webfeed Basics) The Atom file is named “blog.xml” in the same dir.

After i wrote some entry in my blog file “blog.html”, i'd like to be able to press a button, so the current text selection will automatically be added into my atom webfeed file “blog.xml” as a new entry.

Solution

In the beginning few months, i just manually add the new writing from “blog.html” into the “blog.xml” file. But after a while, the pattern is clear, and can be automated. So, here are the major steps:

Here's various pieces of code that is required. I'll start to show, from the smallest components, to the final code that make all this work.

Insert Time Stamp

Here's a command to insert date stamp in Atom format.

(defun insert-date-time ()
  "Insert current date-time string.

Example of inserted text:
2010-01-02T18:01:27-08:00"
  (interactive)
  (insert
   (concat
    (format-time-string "%Y-%m-%dT%T")
    ((lambda (x) (concat (substring x 0 3) ":" (substring x 3 5)))
     (format-time-string "%z")))))

This command simply insert a timestamp into the current cursor position.

Insert Blank Atom Entry

Atom's entry looks like this:

 <entry>
   <title>elisp example: cycle state, lisp symbol property, setting font</title>
   <id>tag:xahlee.org,2010-01-02:234451</id>
   <updated>2010-01-02T15:44:51-08:00</updated>
   <summary>short tutorial</summary>
   <content type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>Added a code snippet on <a href="elisp_examples2.html">More Elisp
Examples</a>. The code shows you how to write a function that cycle
thru states when called repeatedly, and also explains the concepts of
lisp's “symbol property”, setting font in a frame.</p>
</div>
   </content>
  <link rel="alternate" href="http://xahlee.org/emacs/elisp_examples2.html"/>
 </entry>

So, i need a command to insert this entry template.

(defun insert-atom-entry (&optional link-url)
  "Insert a blank Atom webfeed entry template,
 in the current buffer's cursor position.

If LINK-URL is given, it is used in the link tag, e.g.
  <link rel=\"alternate\" href=\"http://xahlee.org/emacs/blog.html\"/>
otherwise the alt link used is:
  <link rel=\"alternate\" href=\"http://xahlee.org/Periodic_dosage_dir/pd.html\"/>"
  (interactive)
(let (textToInsert)
(setq textToInsert
(concat " <entry>\n   <title>ttt</title>\n   <id>"
           (format-time-string
            "tag:xahlee.org,%Y-%m-%d:%H%M%S" (current-time) 1)
           "</id>\n   <updated>"
           (concat
            (format-time-string "%Y-%m-%dT%T")
            ((lambda (x)
               (concat (substring x 0 3) ":"
                       (substring x 3 5))) (format-time-string "%z")))
           "</updated>
   <summary>ttt</summary>
   <content type=\"xhtml\">
<div xmlns=\"http://www.w3.org/1999/xhtml\">
</div>
   </content>
  <link rel=\"alternate\" href=\"" 
  (if link-url
    link-url
"http://xahlee.org/Periodic_dosage_dir/pd.html"
)
  "\"/>
 </entry>\n\n")
)
(insert textToInsert)

)
  )

Again, this command simply insert a template into the current cursor position.

Note that a time stamp is used as part of Atom's “<id>” tag scheme, and those “ttt” used in “<title>” and "<summary>" tags are place holders, so that i can easily move to them to edit them. (letter “t” is under the right hand's index finger in Dvorak keyboard layout.)

The content for “<title>...</title>” and “<summary>...</summary>” content are not automatically created, because when i write a blog usually i don't have a title or summary. Title and Summary are required by Atom, so i write them on the spot.

Updating Blog Date

In the Atom file, at top there's a “updated” tag that looks like this:

<updated>2010-01-02T15:44:51-08:00</updated>

This needs to be updated whenever you have a new entry. So, here's the emacs command for that:

(defun update-blog-date (fpath)
  "Update the Atom RSS updated tag in a Atom file at FPATH.

That is, the first occurance of: <updated>2006-10-10T22:58:42-07:00</updated>"
  (interactive)
  (find-file fpath)
  (goto-char (point-min))
  (let (x1)
    (setq x1 (re-search-forward "<updated>" nil t))
    (delete-region x1 (+ x1 25))
    (insert-date-time)))

This code, opens a file at “fpath”, then search for the string “<updated>”, delete the date text there, and insert a new one. Note that it uses the function “insert-date-time” that we have defined earlier.

make-blog-entry

Finally, here's the command that calls all the above functions to do what i want.

(defun make-blog-entry (begin end)
  "Create a Atom (RSS) entry of the current blog file,
using selected text as content,
and update the Atom file's overall updated” tag.

Note: this command is customized for xah lee's file
structures. Much of things are implicit.

If the current file is
~/web/xahlee_org/emacs/blog.html
Then the blog will be blog.xml

other files paths for blogs are:
~/web/xahlee_org/emacs/blog.html
~/web/xahlee_org/sl/blog.html
~/web/xahlee_org/Periodic_dosage_dir/pd.html

the Atom files names will be same as blog file name but with
suffix “.xml”."
  (interactive "r")
  (let (meat currentFileDir currentFileName blogFileName blogFilePath altUrl)

    (setq meat (buffer-substring-no-properties begin end))
    (setq currentFileName (file-name-nondirectory (buffer-file-name)))
    (setq currentFileDir (file-name-directory (buffer-file-name))) ; ends in slash
    (setq blogFileName (concat (file-name-sans-extension (file-name-nondirectory currentFileName)) ".xml"))
    (setq blogFilePath (concat currentFileDir blogFileName))
    (setq altUrl (concat "http://xahlee.org/"
                         (cond 
                          ((string-match "sl/$" currentFileDir) "sl/blog.html")
                          ((string-match "emacs/$" currentFileDir) "emacs/blog.html")
                          ((string-match "Periodic_dosage_dir/$" currentFileDir) "Periodic_dosage_dir/pd.html")
                          ) 
                         ))

    (find-file blogFilePath)
    (goto-char (point-min))
    (re-search-forward "<entry>" nil t)
    (move-beginning-of-line 1)
    (insert-atom-entry altUrl)
    (re-search-backward "<div xmlns=\"http://www.w3.org/1999/xhtml\">" nil t)
    (re-search-forward ">" nil t)
    (insert meat)
    (update-blog-date blogFilePath)
    (find-file blogFilePath)
    (goto-char (point-min))
    (re-search-forward ">ttt" nil t)
)
)

The code is pretty simple. First, it sets the current selected text to the variable “meat”, by the line:

 (setq meat (buffer-substring-no-properties begin end))

Then, it sets several paths. The current buffer's file path, name, dir, and the corresponding blog file's path, name. It also sets the “altUrl” according to which file it is. If the file is “emacs/blog.html”, then the “link” tag in the Atom entry should be “http://xahlee.org/emacs/blog.xml”.

After the several “setq”, then it opens the blog file, go to the beginning of file, search for the first occurrence of “<entry>”, and that's the point a new entry should be inserted.

It then call “ (insert-atom-entry altUrl)” to insert a new entry template.

Then, it searches backward for the string “<div xmlns="http://www.w3.org/1999/xhtml">”. This is where the “content” part of the entry should be. The code then insert my content “meat” there.

After that, we update the blog updated date by calling “(update-blog-date blogFilePath)”, then we just move pointer to the next occurrence of “ttt”, so that when this code is done, the cursor is right at the Title tag part for user to edit.

Emacs is Beautiful.

Bookmark and Share
2009-01
© 2009 by Xah Lee.