Смешанные имена тегов в cl-who
Я использую cl-who для генерации svg, и он работает нормально, пока мне не понадобится смешанный тег case:
(with-html-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"))))
Для таких ситуаций существует переменная *downcase-tokens-p*. Трудно работать с:
(let ((*downcase-tokens-p* nil))
(with-html-output (*standard-output*)
(:defs
(:|radialGradient| :id "grad1" :cy "20" :fx "10%" :fy "50%" :r "8"))))
Выход:
<defs>
<radialgradient id='grad1' cy='20' fx='10%' fy='50%' r='8'>
</radialgradient>
</defs>
Обтекание с помощью let не имеет никакого эффекта, потому что *downcase-tokens-p* было очевидно установлено T во время расширения макроса.
Итак, нам нужно вытащить Eval:
(let ((*downcase-tokens-p* nil))
(eval
'(with-html-output (*standard-output*)
(:defs
(:|radialGradient| :id "grad1" :cy "20" :fx "10%" :fy "50%" :r "8")))))
Выход:
<DEFS>
<radialGradient ID='grad1' CY='20' FX='10%' FY='50%' R='8'>
</radialGradient>
</DEFS>
Это работает для тега radialGradient, но теперь мне нужно || заверните все остальное.
Какой самый простой способ заставить тег radialGradient правильно отображаться, оставляя все остальное в покое?
Изменить: примеры добавлены.
3 ответа
Вот общее решение:
(defmethod convert-tag-to-string-list :around ((tag t) attr-list body body-fn)
(if (find-if #'lower-case-p (symbol-name tag))
(nconc (list* "<"
(symbol-name tag)
(convert-attributes attr-list))
(list ">")
(funcall body-fn body)
(list (format nil "</~a>" (symbol-name tag))))
(call-next-method)))
Результаты:
CL-USER> (with-html-output (*standard-output*)
(:asdf
(:ASDF
(:|aSDf|
(:|ASDF|)))))
<asdf><asdf><aSDf><asdf></asdf></aSDf></asdf></asdf>
Вы можете изменить случай читателя Lisp.
СОХРАНИТЬ
(setf (readtable-case *readtable*) :preserve)
Отныне все символы CL должны быть написаны в верхнем регистре, но вы можете вносить изменения, локализованные с помощью named-readtables, только в файлах, где вам нужно читать SVG-деревья.
(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"))))
Пишет следующее:
<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
Я бы лично использовал :invert
, но в этом случае вы должны написать все строчные символы SVG в верхнем регистре.
(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"))))
Пишет тоже самое:
<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>"
Но по крайней мере вам код CL не нужно писать в верхнем регистре.
Макросы и / или макро символы
Вы можете вносить свои изменения локально с помощью макросов и макросов.
Сбросить все до их значений по умолчанию:
(setf *downcase-tokens-p* t)
(setf (readtable-case *readtable*) :upcase)
Я бы лично не против поменяться *downcase-tokens-p*
глобально, но если вы действительно хотите, другой подход помимо использования eval
это макроэкспандировать вручную. Для этого примера я использую macroexpand-dammit:
(ql:quickload "macroexpand-dammit")
Затем вы определяете пользовательский макрос:
(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))))))
Наконец, чтобы изменить регистр только для чтения при чтении форм SVG, определите пользовательскую функцию чтения; Я связываю это с #@
последовательность символов:
(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))))
Пример можно переписать так:
(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"))))
Преимущество здесь в том, что ваши изменения применяются только локально, и что существует очень особый синтаксис, который сигнализирует о том, что происходит что-то другое. Если вы в порядке написания кода в верхнем регистре внутри выражения SVG, то вы можете использовать :preserve
вместо. Это зависит от того, что вам удобнее.
Начиная с cl-who-20190710-git, он по умолчанию сохраняет ключевые слова в смешанном регистре в качестве тэгов, поэтому их можно использовать без добавления каких-либо макросов / методов:
(htm
(:|clipPath| :x 0 :y 0 ...))
Для его настройки есть опция *downcase-tokens-p*.
Переопределение метода рендеринга для отдельных тегов:
(defmethod convert-tag-to-string-list ((tag (eql :radialgradient))
attr-list body body-fn)
(nconc (cons "<radialGradient"
(convert-attributes attr-list))
(list ">")
(funcall body-fn body)
(list "</radialGradient>")))
С | удалено:
(with-html-output (*standard-output*)
(:defs
(:radialGradient :id "grad1" :cy "20" :fx "10%" :fy "50%" :r "8")))
Выход:
<defs>
<radialGradient id='grad1' cy='20' fx='10%' fy='50%' r='8'
</radialGradient>
</defs>
Метод convert-tag-to-string-list должен быть определен для каждого используемого SVG-тега со смешанным регистром.