;; content-on-sx — render boundary. ;; ;; Rendering is a message, not a property switch: every block (and the document) ;; answers asHTML and asSx. The internal model carries no presentation — the ;; boundary format is chosen by which message you send. The document folds its ;; children's renderings, so (asHTML doc) / (asSx doc) are pure polymorphic ;; sends with no type dispatch in the SX layer. ;; ;; Escaping happens HERE, at the boundary. asHTML routes text/attrs through ;; String>>htmlEscaped (& < > "); asSx routes them through String>>sxEscaped ;; (\ and ") so values cannot break out of an element or an SX string literal. (define content-bootstrap-render! (fn () (begin (ct-def-method! "String" "htmlEscaped" "htmlEscaped | out i n c | out := ''. n := self size. i := 1. [i <= n] whileTrue: [c := self at: i. (c = $&) ifTrue: [out := out , '&'] ifFalse: [(c = $<) ifTrue: [out := out , '<'] ifFalse: [(c = $>) ifTrue: [out := out , '>'] ifFalse: [(c = $\") ifTrue: [out := out , '"'] ifFalse: [out := out , c asString]]]]. i := i + 1]. ^ out") (ct-def-method! "String" "sxEscaped" "sxEscaped | out i n c | out := ''. n := self size. i := 1. [i <= n] whileTrue: [c := self at: i. (c = $\\) ifTrue: [out := out , '\\\\'] ifFalse: [(c = $\") ifTrue: [out := out , '\\\"'] ifFalse: [out := out , c asString]]. i := i + 1]. ^ out") (ct-def-method! "CtHeading" "asHTML" "asHTML | t | t := level printString. ^ '' , text htmlEscaped , ''") (ct-def-method! "CtText" "asHTML" "asHTML ^ '

' , text htmlEscaped , '

'") (ct-def-method! "CtCode" "asHTML" "asHTML ^ '
' , text htmlEscaped , '
'") (ct-def-method! "CtQuote" "asHTML" "asHTML ^ '
' , text htmlEscaped , '
'") (ct-def-method! "CtImage" "asHTML" "asHTML ^ '\"''") (ct-def-method! "CtEmbed" "asHTML" "asHTML ^ ''") (ct-def-method! "CtDivider" "asHTML" "asHTML ^ '
'") (ct-def-method! "CtList" "asHTML" "asHTML | tag | tag := ordered ifTrue: ['ol'] ifFalse: ['ul']. ^ '<' , tag , '>' , (items inject: '' into: [:a :x | a , '
  • ' , x htmlEscaped , '
  • ']) , ''") (ct-def-method! "CtDoc" "asHTML" "asHTML ^ blocks inject: '' into: [:a :b | a , (b asHTML)]") (ct-def-method! "CtHeading" "asSx" "asSx | t | t := level printString. ^ '(h' , t , ' \"' , text sxEscaped , '\")'") (ct-def-method! "CtText" "asSx" "asSx ^ '(p \"' , text sxEscaped , '\")'") (ct-def-method! "CtCode" "asSx" "asSx ^ '(pre (code \"' , text sxEscaped , '\"))'") (ct-def-method! "CtQuote" "asSx" "asSx ^ '(blockquote \"' , text sxEscaped , '\")'") (ct-def-method! "CtImage" "asSx" "asSx ^ '(img :src \"' , src sxEscaped , '\" :alt \"' , alt sxEscaped , '\")'") (ct-def-method! "CtEmbed" "asSx" "asSx ^ '(iframe :src \"' , url sxEscaped , '\")'") (ct-def-method! "CtDivider" "asSx" "asSx ^ '(hr)'") (ct-def-method! "CtList" "asSx" "asSx | tag | tag := ordered ifTrue: ['ol'] ifFalse: ['ul']. ^ '(' , tag , ' ' , (items inject: '' into: [:a :x | a , '(li \"' , x sxEscaped , '\")']) , ')'") (ct-def-method! "CtDoc" "asSx" "asSx ^ '(article ' , (blocks inject: '' into: [:a :b | a , (b asSx)]) , ')'") true))) ;; ── SX boundary API — pure message sends ── (define asHTML (fn (node) (str (st-send node "asHTML" (list))))) (define asSx (fn (node) (str (st-send node "asSx" (list))))) ;; readable aliases (define render-html asHTML) (define render-sx asSx) (define block-html asHTML) (define block-sx asSx)