Commit9fd3c675Recorded12 Jan 2026Repositorysigil-sxml
sigil-sxml: Add sxml->html and API review
Message
- Add sxml->html for HTML5 output (void elements without trailing slash)
- Handle document wrapper from markdown->sxml (strips wrapper)
- Refactor sxml->xml and sxml->html to share sxml->markup internal
- Add API review in notes/review/sigil-sxml.md
Changed
src/sigil/sxml.sgl | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------
1 file changed, 91 insertions(+), 49 deletions(-)Diff
src/sigil/sxml.sglmodified
@@ -1,7 +1,7 @@
−1
;;; (sigil sxml) - SXML to XML Conversion+1
;;; (sigil sxml) - SXML to XML/HTML Conversion 2
;;; 3
;;; SXML is a standard S-expression representation of XML/HTML documents.−4
;;; This library converts SXML to XML strings for output.+4
;;; This library converts SXML to XML or HTML strings for output. 5
;;; 6
;;; ## SXML Format 7
;;;@@ -16,26 +16,32 @@
16
;;; ```scheme 17
;;; (import (sigil sxml)) 18
;;;−19
;;; (sxml->xml '(html (head (title "Hello"))))+19
;;; ;; HTML output (recommended for web pages)+20
;;; (sxml->html '(html (head (title "Hello")))) 21
;;; ; => "<html><head><title>Hello</title></head></html>" 22
;;;+23
;;; ;; XML output (for XML documents) 24
;;; (sxml->xml '(div (@ (class "main")) "Hello")) 25
;;; ; => "<div class=\"main\">Hello</div>" 26
;;; ``` 27
;;; 28
;;; ## Void Elements 29
;;;−28
;;; Self-closing elements like `<br>`, `<img>`, `<input>` are handled−29
;;; automatically:+30
;;; Void elements like `<br>`, `<img>`, `<input>` are handled automatically.+31
;;; The output format differs between HTML and XML: 32
;;; 33
;;; ```scheme−32
;;; (sxml->xml '(img (@ (src "photo.jpg") (alt "Photo"))))−33
;;; ; => "<img src=\"photo.jpg\" alt=\"Photo\" />"+34
;;; (sxml->html '(img (@ (src "photo.jpg"))))+35
;;; ; => "<img src=\"photo.jpg\">"+36
;;;+37
;;; (sxml->xml '(img (@ (src "photo.jpg"))))+38
;;; ; => "<img src=\"photo.jpg\" />" 39
;;; ``` 40
41
(define-library (sigil sxml) 42
(export 43
;; Main conversion+44
sxml->html ; Convert SXML to HTML string 45
sxml->xml ; Convert SXML to XML string 46
47
;; Helpers@@ -224,28 +230,88 @@
230
(define (raw-text-element? tag) 231
(memq tag raw-text-elements)) 232
−227
;;; Convert SXML content item to XML string−228
;;; When raw? is #t, string content is not escaped (for style/script)−229
(define (content->xml item . raw?)−230
(let ((raw (and (pair? raw?) (car raw?))))−231
(cond−232
((string? item)−233
(if raw item (xml-escape item)))−234
((number? item) (number->string item))−235
;; *raw* element: output content without escaping−236
((and (pair? item) (eq? (car item) '*raw*))−237
(if (null? (cdr item)) "" (cadr item)))−238
((sxml-element? item) (sxml->xml item))−239
((pair? item)−240
;; Could be a list of items−241
(string-join (map (lambda (i) (content->xml i raw)) item)))−242
(else ""))))−243
233
;; ========== Conversion ========== 234
+235
;; Internal: convert SXML content item+236
;; mode is 'html or 'xml, raw? controls escaping for style/script+237
(define (content->markup item mode raw?)+238
(cond+239
((string? item)+240
(if raw? item (xml-escape item)))+241
((number? item) (number->string item))+242
;; *raw* element: output content without escaping+243
((and (pair? item) (eq? (car item) '*raw*))+244
(if (null? (cdr item)) "" (cadr item)))+245
((sxml-element? item) (sxml->markup item mode))+246
((pair? item)+247
;; Could be a list of items+248
(string-join (map (lambda (i) (content->markup i mode raw?)) item)))+249
(else "")))+250
+251
;; Internal: convert SXML tree to markup string+252
;; mode is 'html or 'xml+253
(define (sxml->markup sxml mode)+254
(cond+255
;; String content+256
((string? sxml) (xml-escape sxml))+257
;; Number+258
((number? sxml) (number->string sxml))+259
;; Element+260
((sxml-element? sxml)+261
(let* ((tag (sxml-tag sxml))+262
(content (sxml-content sxml)))+263
(cond+264
;; Document wrapper - just render children (from markdown->sxml)+265
((eq? tag 'document)+266
(string-join (map (lambda (c) (sxml->markup c mode)) content)))+267
;; Void element - HTML uses <br>, XML uses <br />+268
((and (null? content) (void-element? tag))+269
(let ((attr-str (attributes->xml (sxml-attributes sxml))))+270
(if (eq? mode 'html)+271
(string-append "<" (symbol->string tag) attr-str ">")+272
(string-append "<" (symbol->string tag) attr-str " />"))))+273
;; Normal element with content+274
(else+275
(let* ((tag-str (symbol->string tag))+276
(attrs (sxml-attributes sxml))+277
(attr-str (attributes->xml attrs))+278
(raw (raw-text-element? tag)))+279
(string-append "<" tag-str attr-str ">"+280
(string-join (map (lambda (c) (content->markup c mode raw)) content))+281
"</" tag-str ">"))))))+282
;; List of items (flatten)+283
((pair? sxml)+284
(string-join (map (lambda (s) (sxml->markup s mode)) sxml)))+285
;; Anything else+286
(else "")))+287
+288
;;; Convert an SXML tree to an HTML string.+289
;;;+290
;;; Handles nested elements, attributes, text content, and void elements.+291
;;; Void elements like `<br>` and `<img>` are output without a closing slash.+292
;;; Content inside `<style>` and `<script>` tags is not escaped.+293
;;; A `document` wrapper element (from `markdown->sxml`) is stripped.+294
;;;+295
;;; ```scheme+296
;;; (sxml->html '(p "Hello"))+297
;;; ; => "<p>Hello</p>"+298
;;;+299
;;; (sxml->html '(div (@ (class "main")) (p "text")))+300
;;; ; => "<div class=\"main\"><p>text</p></div>"+301
;;;+302
;;; (sxml->html '(img (@ (src "foo.png"))))+303
;;; ; => "<img src=\"foo.png\">"+304
;;;+305
;;; (sxml->html '(document (p "From markdown")))+306
;;; ; => "<p>From markdown</p>"+307
;;; ```+308
(define (sxml->html sxml)+309
(sxml->markup sxml 'html))+310
311
;;; Convert an SXML tree to an XML string. 312
;;; 313
;;; Handles nested elements, attributes, text content, and void elements.+314
;;; Void elements are self-closing with ` />` (XML style). 315
;;; Content inside `<style>` and `<script>` tags is not escaped. 316
;;; 317
;;; ```scheme@@ -259,28 +325,4 @@
325
;;; ; => "<img src=\"foo.png\" />" 326
;;; ``` 327
(define (sxml->xml sxml)−262
(cond−263
;; String content−264
((string? sxml) (xml-escape sxml))−265
;; Number−266
((number? sxml) (number->string sxml))−267
;; Element−268
((sxml-element? sxml)−269
(let* ((tag (sxml-tag sxml))−270
(tag-str (symbol->string tag))−271
(attrs (sxml-attributes sxml))−272
(content (sxml-content sxml))−273
(attr-str (attributes->xml attrs))−274
(raw (raw-text-element? tag)))−275
(if (and (null? content) (void-element? tag))−276
;; Self-closing void element−277
(string-append "<" tag-str attr-str " />")−278
;; Normal element with content−279
(string-append "<" tag-str attr-str ">"−280
(string-join (map (lambda (c) (content->xml c raw)) content))−281
"</" tag-str ">"))))−282
;; List of items (flatten)−283
((pair? sxml)−284
(string-join (map content->xml sxml)))−285
;; Anything else−286
(else "")))))+328
(sxml->markup sxml 'xml))))