A few days ago, I posed an Elisp programming challenge that asked you to make the record
(record (date "2005-02-21T18:57:39") (millis 1109041059800) (sequence 1) (logger nil) (level 'SEVERE) (class "java.util.logging.LogManager$RootLogger") (method 'log) (thread 10) (emessage "A very very bad thing has happened!") (exception (emessage "java.lang.Exception") (frame (class "logtest") (method 'main) (line 30))))
executable in such a way that it transforms itself into the equivalent XML record when executed.
On the day I posed the challenge, I woke up thinking that making the record transform itself into XML was the proper way to turn it into XML rather than the way I described in Converting S-Expression To XML In Emacs. It seemed like a direct application of the ideas in my More Fun With Log Files Stored As Lisp post and I decided to program it just for fun with no idea of turning it into a post.
A couple of hours later I was still flailing around so maybe the application wasn’t as direct as I thought. Actually, the idea is the same but because you’re not transforming it back into Lisp it’s a little trickier.
In any event, here’s how I solved the problem. First define all the tags except record
to be functions of the form
(defun date (&rest args) (toxml 'date args))
As I explained in the Exploratory Programming In Emacs post that involved little more than copying the list of tags and a keyboard macro. The toxml
function is
(defun toxml (tag args) (with-output-to-string (princ (format "\n<%s>" tag)) (mapc 'princ args) (princ (format "</%s>" tag))))
The idea is that each sexpr returns a string of the arguments wrapped in <tag>…</tag> where tag
is the symbol in the function slot of the sexpr. That is, (date "2005-02-21T18:57:39")
is transformed to "<date>2005-02-21T18:57:39</date>"
. If we temporarily alias record
to list
and executed the record sexpr, we get
(" <date>2005-02-21T18:57:39</date>" " <millis>1109041059800</millis>" " <sequence>1</sequence>" " <logger>nil</logger>" " <level>SEVERE</level>" " <class>java.util.logging.LogManager$RootLogger</class>" " <method>log</method>" " <thread>10</thread>" " <emessage>A very very bad thing has happened!</emessage>" " <exception> <emessage>java.lang.Exception</emessage> <frame> <class>logtest</class> <method>main</method> <line>30</line></frame></exception>")
which shows what the record
function will see when it gets called. Note that each string begins with a newline so the ” ” at the end of each line is a close quote followed by an open quote.
Finally, we need to define a record
function to stitch the strings together and get rid of that annoying nil
in <logger>nil</logger>
.
(defun record (&rest args) (princ "<record>") (princ (replace-regexp-in-string ">nil<" "><" (apply 'concat args))) (princ "</record>"))
The princ
on the third line gets rid of the quote marks that the concat
places around the final result of the apply
.
Now when we execute the record we get
<record> <date>2005-02-21T18:57:39</date> <millis>1109041059800</millis> <sequence>1</sequence> <logger></logger> <level>SEVERE</level> <class>java.util.logging.LogManager$RootLogger</class> <method>log</method> <thread>10</thread> <emessage>A very very bad thing has happened!</emessage> <exception> <emessage>java.lang.Exception</emessage> <frame> <class>logtest</class> <method>main</method> <line>30</line></frame></exception></record>
Update: Let me mention again that the inspiration for this series of posts and the sample record sexpr are from Steve Yegge’s The Emacs Problem. Be sure to give it a read if you haven’t already.