1

我正在使用 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标签正确显示的最简单方法,同时不理会其他所有内容?

编辑:添加示例。

4

4 回答 4

1

覆盖单个标签的呈现方法:

(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 方法。

于 2017-09-02T14:50:08.693 回答
1

这是一个通用的解决方案:

(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>
于 2017-09-02T16:18:59.673 回答
1

你可以改变 Lisp 阅读器的情况。

保存

(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 代码不需要写成大写。

宏和/或宏字符

您可以使用宏和宏字符在本地进行更改。

将所有内容重置为默认值:

(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 表单时改变 readtable 的大小写,定义一个自定义的 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改用。这取决于什么对你更方便。

于 2017-09-02T17:59:48.113 回答
1

从 cl-who-20190710-git 开始,它默认保留混合大小写的关键字作为标记名,因此可以在不添加任何宏/方法的情况下使用它们:

 (htm 
   (:|clipPath| :x 0 :y 0 ...))

有 *downcase-tokens-p* 选项来配置它。

于 2019-11-18T18:46:26.013 回答