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.