diff --git a/hsx-test.asd b/hsx-test.asd index 06fbb9c..9dd4f5a 100644 --- a/hsx-test.asd +++ b/hsx-test.asd @@ -2,8 +2,8 @@ :class :package-inferred-system :pathname "tests" :depends-on ("rove" + "hsx-test/utils" "hsx-test/element" - "hsx-test/escaper" "hsx-test/group" "hsx-test/hsx") :perform (test-op (o c) (symbol-call :rove :run c :style :dot))) diff --git a/src/element.lisp b/src/element.lisp index c9e61df..955d2a5 100644 --- a/src/element.lisp +++ b/src/element.lisp @@ -1,8 +1,9 @@ (defpackage #:hsx/element (:use #:cl) - (:import-from #:hsx/escaper + (:import-from #:hsx/utils #:escape-html-attribute - #:escape-html-text-content) + #:escape-html-text-content + #:minify) (:import-from #:hsx/group #:self-closing-tag-p #:non-escaping-tag-p) @@ -118,18 +119,19 @@ (string-downcase (element-type element))) (defmethod render-props ((element tag)) - (with-output-to-string (stream) - (loop - :for (key value) :on (element-props element) :by #'cddr - :do (let ((key-str (string-downcase key))) - (if (typep value 'boolean) - (format stream - "~@[ ~a~]" - (and value key-str)) - (format stream - " ~a=\"~a\"" - key-str - (escape-html-attribute value))))))) + (minify + (with-output-to-string (stream) + (loop + :for (key value) :on (element-props element) :by #'cddr + :do (let ((key-str (string-downcase key))) + (if (typep value 'boolean) + (format stream + "~@[ ~a~]" + (and value key-str)) + (format stream + " ~a=\"~a\"" + key-str + (escape-html-attribute value)))))))) (defmethod render-children ((element tag)) (mapcar (lambda (child) diff --git a/src/escaper.lisp b/src/utils.lisp similarity index 56% rename from src/escaper.lisp rename to src/utils.lisp index f611a70..4678a1d 100644 --- a/src/escaper.lisp +++ b/src/utils.lisp @@ -1,10 +1,11 @@ -(defpackage #:hsx/escaper +(defpackage #:hsx/utils (:use #:cl) (:import-from #:alexandria #:alist-hash-table) (:export #:escape-html-attribute - #:escape-html-text-content)) -(in-package #:hsx/escaper) + #:escape-html-text-content + #:minify)) +(in-package #:hsx/utils) (defparameter *text-content-escape-map* (alist-hash-table @@ -22,7 +23,7 @@ '((#\" . """)))) (defun escape-char (char escape-map) - (or (gethash char escape-map) + (or (gethash char escape-map) char)) (defun escape-string (string escape-map) @@ -38,3 +39,19 @@ (defun escape-html-attribute (text) (escape-string text *attribute-escape-map*)) + +(defun minify (input-string) + (with-output-to-string (out) + (let ((previous-space-p nil)) + (loop for char across input-string do + (cond + ((whitespace-p char) + (unless previous-space-p + (write-char #\Space out)) + (setf previous-space-p t)) + (t + (write-char char out) + (setf previous-space-p nil))))))) + +(defun whitespace-p (char) + (member char '(#\Space #\Newline #\Tab #\Return) :test #'char=)) diff --git a/tests/escaper.lisp b/tests/escaper.lisp deleted file mode 100644 index 9de77cf..0000000 --- a/tests/escaper.lisp +++ /dev/null @@ -1,14 +0,0 @@ -(defpackage #:hsx-test/escaper - (:use #:cl - #:rove - #:hsx/escaper)) -(in-package #:hsx-test/escaper) - -(deftest escaper-test - (testing "escape-html-attribute" - (ok (string= ""foo"" - (escape-html-attribute "\"foo\"")))) - - (testing "escape-html-text-content" - (ok (string= "&<>"'/`=" - (escape-html-text-content "&<>\"'/`="))))) diff --git a/tests/utils.lisp b/tests/utils.lisp new file mode 100644 index 0000000..d9e8c3d --- /dev/null +++ b/tests/utils.lisp @@ -0,0 +1,23 @@ +(defpackage #:hsx-test/utils + (:use #:cl + #:rove + #:hsx/utils)) +(in-package #:hsx-test/utils) + +(deftest text-util-test + (testing "escape-html-attribute" + (ok (string= ""foo"" + (escape-html-attribute "\"foo\"")))) + + (testing "escape-html-text-content" + (ok (string= "&<>"'/`=" + (escape-html-text-content "&<>\"'/`=")))) + + (testing "minify" + ;; Test with Alpine.js + (ok (string= (minify "{ + open: false, + get isOpen() { return this.open }, + toggle() { this.open = ! this.open }, + }") + "{ open: false, get isOpen() { return this.open }, toggle() { this.open = ! this.open }, }"))))