cl-who 中混合大小写的标签名称
Mixed case tag names in cl-who
我正在使用 cl-who 来生成 svg,在我需要混合大小写标签之前它工作正常:
(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 标签正确显示同时不影响其他所有内容的最简单方法是什么?
编辑:添加了示例。
覆盖单个标签的渲染方法:
(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>
需要为每个正在使用的大小写混合的 SVG 标签定义一个 convert-tag-to-string-list 方法。
这是一个通用的解决方案:
(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 reader 的大小写。
保留
(setf (readtable-case *readtable*) :preserve)
从现在开始,所有 CL 符号都必须以大写形式书写,但您可以仅在需要读取 SVG 树的文件中使用 named-readtables 进行本地化更改。
(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
,但在那种情况下,您必须将所有小写 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代码不用大写
宏and/or个宏字符
您可以使用宏和宏字符在本地进行更改。
将所有内容重置为默认值:
(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 表单时更改可读表的大小写,定义自定义 reader 函数;我将它绑定到 #@
字符序列:
(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 开始,它默认将大小写混合的关键字保留为标记名,因此无需添加任何 macros/methods:
即可使用它们
(htm
(:|clipPath| :x 0 :y 0 ...))
有 *downcase-tokens-p* 选项来配置它。
我正在使用 cl-who 来生成 svg,在我需要混合大小写标签之前它工作正常:
(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 标签正确显示同时不影响其他所有内容的最简单方法是什么?
编辑:添加了示例。
覆盖单个标签的渲染方法:
(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>
需要为每个正在使用的大小写混合的 SVG 标签定义一个 convert-tag-to-string-list 方法。
这是一个通用的解决方案:
(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 reader 的大小写。
保留
(setf (readtable-case *readtable*) :preserve)
从现在开始,所有 CL 符号都必须以大写形式书写,但您可以仅在需要读取 SVG 树的文件中使用 named-readtables 进行本地化更改。
(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
,但在那种情况下,您必须将所有小写 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代码不用大写
宏and/or个宏字符
您可以使用宏和宏字符在本地进行更改。
将所有内容重置为默认值:
(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 表单时更改可读表的大小写,定义自定义 reader 函数;我将它绑定到 #@
字符序列:
(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 开始,它默认将大小写混合的关键字保留为标记名,因此无需添加任何 macros/methods:
即可使用它们 (htm
(:|clipPath| :x 0 :y 0 ...))
有 *downcase-tokens-p* 选项来配置它。