aboutsummaryrefslogtreecommitdiff
path: root/weeb/sxml.scm
blob: b51abbce9b5ff1690f5391996c91f04369136f94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
;;; Weeb --- Web microframework for GNU Guile
;;; Copyright © 2026 Jakob Kreuze <zerodaysfordays@sdf.org>
;;;
;;; This file is part of Weeb.
;;;
;;; Weeb is free software: you can redistribute it and/or modify it
;;; under the terms of the GNU Affero General Public License as
;;; published by the Free Software Foundation, either version 3 of the
;;; License, or (at your option) any later version.
;;;
;;; Weeb is distributed in the hope that it will be useful, but WITHOUT
;;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
;;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
;;; Public License for more details.
;;;
;;; You should have received a copy of the GNU Affero General Public
;;; License along with Weeb. If not, see <https://www.gnu.org/licenses/>.

(define-module (weeb sxml)
  #:use-module (ice-9 match)
  #:use-module (ice-9 string-fun)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:export (sxml->html
            sxml->html-document))

(define (self-closing? tag-name)
  (memq tag-name '(img br hr input)))

(define (emit-raw? tag-name)
  (memq tag-name '(style script)))

(define (sxml-tag? elt)
  (match elt
    ((_tag-name ('@ _attr ...) _content ...) #t)
    ((_tag-name _content ...) #t)
    (_ #f)))

(define (extract-tag-name tag)
  (first tag))

(define (extract-attributes tag)
  (match tag
    ((_tag-name ('@ attrs ...) _content ...) attrs)
    (_ '())))

(define (extract-children tag)
  (match tag
    ((_tag-name ('@ _attrs ...) content ...) content)
    ((_tag-name content ...) content)
    (_ '())))

;; TODO: We should probably encode other entities as well, but there are
;; hundreds, so we need a more efficient way of substituting first.
(define (html-encode str)
  (define %entities
    '((#\& . "&amp;")
      (#\< . "&lt;")
      (#\> . "&gt;")
      (#\" . "&quot;")
      (#\' . "&#39;")))
  (define (iter s replace)
    (match replace
      (((char . entity) rest ...)
       (iter (string-replace-substring s (string char) entity) rest))
      (() s)))
  (iter str %entities))

(define (emit-sxml-tree tree)
  (match tree
    ((? sxml-tag?)
     (let ((tag-name (extract-tag-name tree)))
       (display "<")
       (display tag-name)
       (for-each (match-lambda
                   ((key value)
                    (display " ")
                    (display key)
                    (display "=\"")
                    (display value)
                    (display "\"")))
                 (extract-attributes tree))
       (display ">")
       (unless (self-closing? tag-name)
         (if (emit-raw? tag-name)
             (display (second tree))
             (for-each emit-sxml-tree (extract-children tree)))
         (display "</")
         (display tag-name)
         (display ">"))))
    ((? string?) (begin (display (html-encode tree))))
    ((? number?) (begin (display tree)))))

(define (emit-sxml-document document)
  (display "<!DOCTYPE html>")
  (emit-sxml-tree document))

(define (sxml->html tree)
  (with-output-to-string (cut emit-sxml-tree tree)))
(define (sxml->html-document tree)
  (with-output-to-string (cut emit-sxml-document tree)))

© 2015 - 2026 Jakob L. Kreuze