CL-WHO основана на макросах, которые генерируют операторы записи, и автоматически печатаются все формы, начинающиеся с ключевого слова, а также значения аргументов.Другие формы оцениваются только (например, на наличие побочных эффектов) и не распечатываются автоматически.Вот почему CL-WHO вводит макросы str
, fmt
, esc
и htm
, которые заставляют печатать их аргументы (по-разному).
Ваш код:
(defun bar ()
(with-html-output-to-string
(*standard-output* nil :prologue t)
(htm "This will NOT show up in the html stream")))
Возвращаемое значение является строкой, так как вы используете with-html-output-to-string
.*standard-output*
временно привязан к потоку, отличному от того, что снаружи bar
, только для создания строки, возвращаемой вызывающей стороне, здесь foo
.Строка не печатается (печатаются только формы, которые являются константными строками в позиции содержимого).
Вы можете принудительно записать возвращенный сгенерированный HTML-код, используя str
, но ИМХО лучший вариант - написать напрямую втот же поток вывода, что и для вызывающего, вместо создания промежуточных строк.
Непосредственная запись в поток
В основном используйте with-html-output
:
Я предпочитаю не использовать *standard-output*
, но поток, который используется только для HTML.Это препятствует тому, чтобы другие библиотеки когда-либо записывали что-либо нежелательное на страницу HTML.Вы также можете передать поток каждой вспомогательной функции, но лучше использовать специальные переменные в этих случаях.
Давайте использовать простые макросы, чтобы иметь более легкий синтаксис и применять наши собственные соглашения.
Следующее определяет пакет и настраивает CL-WHO для испускания кода HTML5.Это должно быть сделано до того, как макросы будут развернуты, поскольку во время макроразложения используются устанавливаемые специальные переменные:
(defpackage :web (:use :cl :cl-who))
(in-package :web)
;; Evaluate before CL-WHO macro are expanded
(eval-when (:compile-toplevel :load-toplevel :execute)
(setf (html-mode) :html5))
Определите поток, которым мы можем управлять, по умолчанию привязанный к тому, что связано с *standard-output*
, когда мы open поток (не тогда, когда мы определяем переменную):
(defvar *html-output* (make-synonym-stream '*standard-output*)
"Use a dedicated stream variable for html")
Кроме того, определите общий уровень отступа:
(defvar *indent* 2
"Default indentation")
Существует два макроса, один для фрагментоввстроены во вспомогательные функции, которые пишут в нашем потоке, и одну для html-страниц верхнего уровня, которые возвращают строку.
(defmacro with-html (&body body)
"Establish an HTML context (intended for auxiliary functions)."
`(with-html-output (*html-output* nil :indent *indent*)
,@body))
(defmacro with-html-page (&body body)
"Return an HTML string (intended for top-level pages)."
`(with-html-output-to-string (*html-output* nil :prologue t :indent *indent*)
,@body))
Пример использования:
(defun my-section (title)
(with-html
(:h1 (esc title))
(:p "lorem ipsum")))
(defun my-page ()
(with-html-page
(my-section "title")))
Вызов (my-page)
возвращает:
"<!DOCTYPE html>
<h1>title
</h1>
<p>lorem ipsum
</p>"
См. также менее известный https://github.com/ruricolist/spinneret.