You could change the Lisp reader's case.
PRESERVE
(setf (readtable-case *readtable*) :preserve)
From now on, all CL symbols must be written in uppercase, but you could make the changes localized using named-readtables only in files where you need to read SVG trees.
(DEFPACKAGE :TWHO (:USE :CL :CL-WHO))
(IN-PACKAGE :TWHO)
(SETF *DOWNCASE-TOKENS-P* NIL)
(WITH-HTML-OUTPUT (*STANDARD-OUTPUT* *STANDARD-OUTPUT* :INDENT T)
(:defs
(:radialGradient :id "grad1" :cy "20" :fx "10%" :fy "50%" :r "8"
(:stop :offset "0%" :stop-color "#fff")
(:stop :offset "100%" :stop-color "#000"))))
Writes the following:
<defs>
<radialGradient id='grad1' cy='20' fx='10%' fy='50%' r='8'>
<stop offset='0%' stop-color='#fff'></stop>
<stop offset='100%' stop-color='#000'></stop>
</radialGradient>
</defs>
INVERT
I'd personally use :invert
, but in that case you have to write all the lowercase SVG symbols in uppercase.
(SETF (READTABLE-CASE *READTABLE*) :INVERT)
(with-html-output (*standard-output* *standard-output* :indent t)
(:DEFS
(:radialGradient :ID "grad1" :CY "20" :FX "10%" :FY "50%" :R "8"
(:STOP :OFFSET "0%" :STOP-COLOR "#fff")
(:STOP :OFFSET "100%" :STOP-COLOR "#000"))))
Writes the same thing:
<defs>
<radialGradient id='grad1' cy='20' fx='10%' fy='50%' r='8'>
<stop offset='0%' stop-color='#fff'></stop>
<stop offset='100%' stop-color='#000'></stop>
</radialGradient>
</defs>"
But at least you the CL code does not need to be written in uppercase.
Macros and/or macro characters
You can make your changes local with macros and macro characters.
Reset everything to their default values:
(setf *downcase-tokens-p* t)
(setf (readtable-case *readtable*) :upcase)
I would personally not mind changing *downcase-tokens-p*
globally, but if you really want to, another approach besides using eval
is to macroexpand manually. For this example I am using macroexpand-dammit:
(ql:quickload "macroexpand-dammit")
Then, you define a custom macro:
(defmacro with-svg-output ((stream) &body body)
(let ((*downcase-tokens-p* nil))
(let ((stream% (copy-symbol :stream)))
(macroexpand-dammit:macroexpand-dammit
`(let ((,stream% ,stream))
(with-html-output (,stream% ,stream% :indent t)
,@body))))))
Finally, to change the readtable's case only when reading SVG forms, define a custom reader function; I bind it to the #@
character sequence:
(set-dispatch-macro-character
#\# #\@
(lambda (stream &rest args)
(declare (ignore args))
(let ((*readtable* (copy-readtable)))
(setf (readtable-case *readtable*) :invert)
(read stream t nil t))))
The example can be rewritten as:
(with-svg-output (*standard-output*)
#@(:DEFS
(:radialGradient :ID "grad1" :CY "20" :FX "10%" :FY "50%" :R "8"
(:STOP :OFFSET "0%" :STOP-COLOR "#fff")
(:STOP :OFFSET "100%" :STOP-COLOR "#000"))))
The advantage here is that your changes are only applied locally and that there is a very distinctive syntax which signals that there is something different happening. If you are ok with writing code in uppercase inside the SVG expression, then you can use :preserve
instead. It depends on what is more convenient for you.