#lang racket/base
(require (planet neil/xexp:1:0))
(define (error-html-writing-foreign-filter context object)
(raise-invalid-xexp-error
'error-html-writing-foreign-filter
#:expected (string-append "valid foreign object in "
(case context
((content) "content")
((attribute) "attribute")
((attribute-value) "attribute value")
(else (format "~S(?!?!?!)" context)))
" context")
#:invalid-xexp object))
(define current-html-writing-foreign-filter
(make-parameter error-html-writing-foreign-filter))
(define (write-html-attribute-value-char chr out)
(case chr
((#\") (display """ out))
((#\<) (display "<" out))
((#\>) (display ">" out))
((#\&) (display "&" out))
(else (display chr out))))
(define (write-html-attribute-value-string str out)
(let ((len (string-length str)))
(let loop ((i 0))
(if (< i len)
(begin (write-html-attribute-value-char (string-ref str i) out)
(loop (+ 1 i)))
(void)))))
(define (write-html-attribute-value-part/fixed thing out foreign-filter)
(cond ((string? thing)
(write-html-attribute-value-string thing out))
((char? thing)
(write-html-attribute-value-char thing out))
((pair? thing)
(case (car thing)
((&)
(%html-writing:write-html-entity-ref-args (cdr thing) out))
((*splice* *SPLICE*)
(%html-writing:write-html-attribute-value-part-list/fixed
(cdr thing)
out
foreign-filter))
(else (raise-invalid-xexp-error
'write-html-attribute-value-part/fixed
#:expected "pair object in xexp attribute value part"
#:invalid-xexp thing))))
(else
(let ((filtered (foreign-filter 'attribute-value thing)))
(if (null? filtered)
(void)
(write-html-attribute-value-part/fixed filtered
out
foreign-filter))))))
(define (%html-writing:write-html-attribute-value/fixed val out foreign-filter)
(or (null? val)
(begin
(display "=\"" out)
(%html-writing:write-html-attribute-value-part-list/fixed
val
out
foreign-filter)
(write-char #\" out))))
(define (write-html-attribute-value
val
(out (current-output-port))
(foreign-filter (current-html-writing-foreign-filter)))
(%html-writing:write-html-attribute-value/fixed val out foreign-filter))
(define (%html-writing:write-html-attribute-value-part-list/fixed
part-list
out
foreign-filter)
(for-each (lambda (thing)
(write-html-attribute-value-part/fixed thing
out
foreign-filter))
part-list))
(define (write-html-attribute
attr
(out (current-output-port))
(foreign-filter (current-html-writing-foreign-filter)))
(%html-writing:write-html-attribute/fixed attr out foreign-filter))
(define (%html-writing:write-html-attribute/fixed attr out foreign-filter)
(cond ((pair? attr)
(let ((name (car attr)))
(or (symbol? name)
(raise-invalid-xexp-error '%html-writing:write-html-attribute/fixed
#:expected "attribute name"
#:invalid-xexp attr))
(or (eq? name '@)
(begin (write-char #\space out)
(display name out)
(let ((val (cdr attr)))
(if (null? val)
(begin (display "=\"" out)
(display name out)
(write-char #\" out))
(%html-writing:write-html-attribute-value/fixed
val
out
foreign-filter)))))))
(else
(let ((v (foreign-filter 'attribute attr)))
(cond ((and (pair? v) (memq (car v) '(*splice* *SPLICE*)))
(%html-writing:write-html-attribute-list/fixed
(cdr v)
out
foreign-filter))
((null? v) (void))
(else
(%html-writing:write-html-attribute/fixed
v
out
foreign-filter)))))))
(define (write-html-attribute-list
attr-list
(out (current-output-port))
(foreign-filter (current-html-writing-foreign-filter)))
(%html-writing:write-html-attribute-list/fixed attr-list
out
foreign-filter))
(define (%html-writing:write-html-attribute-list/fixed attr-list
out
foreign-filter)
(for-each (lambda (attr)
(%html-writing:write-html-attribute/fixed
attr
out
foreign-filter))
attr-list))
(define (write-html-attribute-or-list
attr-or-list
(out (current-output-port))
(foreign-filter (current-html-writing-foreign-filter)))
(%html-writing:write-html-attribute-or-list/fixed attr-or-list
out
foreign-filter))
(define (%html-writing:write-html-attribute-or-list/fixed attr-or-list
out
foreign-filter)
(cond
((pair? attr-or-list)
(if (symbol? (car attr-or-list))
(%html-writing:write-html-attribute/fixed attr-or-list
out
foreign-filter)
(%html-writing:write-html-attribute-list/fixed attr-or-list
out
foreign-filter)))
((null? attr-or-list) (void))
(else
(%html-writing:write-html-attribute-or-list/fixed
(foreign-filter 'attribute attr-or-list)
out
foreign-filter))))
(define (write-html-decl/fixed thing out)
(or (memq (car thing) '(*decl* *DECL*))
(raise-invalid-xexp-error 'write-html-decl/fixed
#:expected "HTML DECL"
#:invalid-xexp thing))
(let ((head (car (cdr thing))))
(display "<!" out)
(display (symbol->string head) out)
(for-each
(lambda (n)
(cond ((symbol? n)
(write-char #\space out)
(display (symbol->string n) out))
((string? n)
(display " \"" out)
(write-html-attribute-value-string n out)
(write-char #\" out))
(else (raise-invalid-xexp-error 'write-html-decl/fixed
#:expected "HTML DECL"
#:invalid-xexp thing))))
(cdr (cdr thing)))
(write-char #\> out)))
(define (write-html-pi/fixed thing out)
(or (memq (car thing) '(*pi* *PI*))
(raise-invalid-xexp-error 'write-html-pi/fixed
#:expected "HTML PI"
#:invalid-xexp thing))
(display "<?" out)
(display (car (cdr thing)) out)
(write-char #\space out)
(display (car (cdr (cdr thing))) out)
(display "?>" out))
(define (write-html-entity-ref thing
(out (current-output-port)))
(if (and (pair? thing)
(eqv? #\& (car thing)))
(%html-writing:write-html-entity-ref-args (cdr thing) out)
(raise-invalid-xexp-error 'write-html-entity-ref
#:expected "entity reference"
#:invalid-xexp thing)))
(define (%html-writing:write-html-entity-ref-args args out)
(let ((val (car args)))
(if (symbol? val)
(if (null? (cdr args))
(begin (write-char #\& out)
(display val out)
(write-char #\; out))
(raise-invalid-xexp-error
'write-html
#:expected "entity reference args (invalid extra args!!!)"
#:invalid-xexp args))
(raise-invalid-xexp-error
'write-html
#:expected "entity reference args (non-symbol!!!)"
#:invalid-xexp args))))
(define (write-html/fixed xexp out foreign-filter)
(letrec
((write-xexp-text-string
(lambda (str out)
(let ((len (string-length str)))
(let loop ((i 0))
(if (< i len)
(begin (display (let ((c (string-ref str i)))
(case c
((#\&) "&")
((#\<) "<")
((#\>) ">")
(else c)))
out)
(loop (+ 1 i)))
(void))))))
(write-xexp-text-char
(lambda (chr out)
(case chr
((#\&) (display "&" out))
((#\<) (display "<" out))
((#\>) (display ">" out))
(else (display "&#" out)
(display (char->integer chr) out)
(display ";" out)))))
(do-thing
(lambda (thing)
(cond ((string? thing) (write-xexp-text-string thing out))
((char? thing) (write-xexp-text-char thing out))
((pair? thing) (if (not (null? thing))
(do-list-thing thing)
(void)))
(else (do-thing (foreign-filter 'content thing))))))
(do-list-thing
(lambda (thing)
(let ((head (car thing)))
(cond ((symbol? head)
(case head
((*comment* *COMMENT*)
(display "<!-- " out)
(let ((text (car (cdr thing))))
(if (string? text)
(display text out)
(raise-invalid-xexp-error 'write-html
#:expected "comment text"
#:invalid-xexp thing)))
(or (null? (cdr (cdr thing)))
(raise-invalid-xexp-error 'write-html
#:expected "comment body"
#:invalid-xexp thing))
(display " -->" out))
((*decl* *DECL*)
(write-html-decl/fixed thing out))
((*pi* *PI*)
(write-html-pi/fixed thing out))
((*top* *TOP*)
(for-each do-thing (cdr thing)))
((@)
(raise-invalid-xexp-error
'write-html
#:expected "element position thing (not element attributes)"
#:invalid-xexp thing))
((&)
(%html-writing:write-html-entity-ref-args (cdr thing)
out))
(else
(write-char #\< out)
(display head out)
(let* ((rest (cdr thing)))
(or (null? rest)
(let ((second (car rest)))
(and (pair? second)
(not (null? second))
(eq? (car second) '@)
(begin
(%html-writing:write-html-attribute-list/fixed
(cdr second)
out
foreign-filter)
(set! rest (cdr rest))))))
(if (memq head always-empty-html-elements)
(display " />" out)
(begin (write-char #\> out)
(for-each do-thing rest)
(display "</" out)
(display (symbol->string head) out)
(write-char #\> out)))))))
(else
(raise-invalid-xexp-error 'write-html
#:expected "xexp list"
#:invalid-xexp thing)))))))
(or (null? xexp) (do-thing xexp))
(void)))
(define (write-html
xexp
(out (current-output-port))
(foreign-filter (current-html-writing-foreign-filter)))
(write-html/fixed xexp out foreign-filter))
(define (xexp->html xexp)
(let ((os (open-output-string)))
(write-html xexp os)
(get-output-string os)))
(provide
current-html-writing-foreign-filter
error-html-writing-foreign-filter
write-html
write-html-decl/fixed
write-html-pi/fixed
write-html/fixed
write-html-attribute
write-html-attribute-list
write-html-attribute-or-list
write-html-attribute-value
write-html-attribute-value-string
write-html-entity-ref
xexp->html)