The implementation consists of two functions. The first,
sexpr->xml, does the actual conversion, while the second,
convert-to-xml, takes care of Emacs bookkeeping details and acts as a driver. For convenience,
convert-to-xml takes an sexpr as its input but it would be easy to have it operate on a region or even a whole buffer.
We'll use the example sexpr from Yegge's The Emacs Problem post. Note that, again for simplicity, I am dealing with only a single record but everything would work exactly the same if there were multiple records wrapped in a
'(log …) sexpr.
'(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) (message "A very very bad thing has happened!") (exception (message "java.lang.Exception") (frame (class "logtest") (method 'main) (line 30))))
The converter is very simple.
1: (defun sexpr->xml (sexpr) 2: (let ((tag (car sexpr))) 3: (princ (format "<%s>" tag)) 4: (dolist (o (cdr sexpr)) 5: (if (atom o) 6: (princ (format "%s " o)) 7: (sexpr->xml o))) 8: (princ (format "</%s>" tag))))
It's passed an sexpr whose first symbol is the XML tag. That gets saved away in the
let on line 2 and printed as the opening tag on line 3. The
dolist loop in lines 4–7 looks at each of the other objects in the sexpr. If an object is not another sexpr, it is printed with a trailing space. If it is another sexpr,
sexpr->xml is called recursively on line 7 to process it. When all the objects in the sexpr have been processed, the end tag is printed on line 8.
The output of
sexpr->xml is a single line of XML with no formatting at all. Also, any
nils will appear explicitly in the XML instead of the tag pair being empty and all the quoted symbols will be wrapped in
</quote> tags because the lisp reader turns
Now let's look at the driver function:
1: (defun convert-to-xml (sexpr) 2: (with-output-to-temp-buffer "*XML*" 3: (sexpr->xml sexpr) 4: (set-buffer "*XML*") 5: (xml-mode) 6: (replace-regexp "\\bnil\\b\\|<quote>\\|</quote>" "" nil (point-min) (point-max)) 7: (sgml-pretty-print (point-min) (point-max))))
Lines 2 and 3 call
sexpr->xml and arrange for its output to go into a buffer named
sexpr->xml returns, the
*XML* buffer is selected and set to
replace-regexp on line 6 deletes any occurrences of
nil and gets rid of
</quote> tags. Finally,
sgml-pretty-print is called to format the XML nicely.
That's a lot of work for not very much code. We could, of course, take care of the formatting and fixing up the
<quote> problems right in
sexpr->xml but I wanted to show how simple the conversion can be without a lot of busy details. Besides, we have the power of Emacs so it would foolish not to use it.
The final result of running
convert-to-xml on our sample sexpr is
<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> <message>A very very bad thing has happened! </message> <exception> <message>java.lang.Exception </message> <frame> <class>logtest </class> <method>main </method> <line>30 </line> </frame> </exception> </record>
Any Elisp coders out there might want to consider what a translator in the other direction (XML to s-expressions) would look like. Not having s-expressions to leverage makes the processing a little more difficult but not by much.