;; content-on-sx — portable data serialization. ;; ;; Converts documents to/from a plain SX data form, decoupling storage and ;; transport from the Smalltalk instance shape. A document becomes ;; {:id :title :slug :tags :blocks (list block-data)} ;; and a block becomes {:id :type :fields {...}} (section children recurse). ;; content/from-data reconstructs real block objects. ;; ;; Requires (loaded by harness): block.sx, doc.sx, meta.sx, section.sx ;; (mk-section), table.sx (mk-table). ;; ── to-data ── (define content/-fd-loop (fn (ks ivs acc) (if (= (len ks) 0) acc (let ((k (first ks))) (if (= k "id") (content/-fd-loop (rest ks) ivs acc) (content/-fd-loop (rest ks) ivs (assoc acc k (if (= k "children") (map block->data (get ivs k)) (get ivs k))))))))) (define block->data (fn (b) {:fields (content/-fd-loop (keys (get b :ivars)) (get b :ivars) {}) :id (blk-id b) :type (blk-type b)})) (define content/to-data (fn (doc) {:blocks (map block->data (doc-blocks doc)) :slug (doc-slug doc) :id (doc-id doc) :title (doc-title doc) :tags (doc-tags doc)})) ;; ── from-data ── (define content/-field-pairs (fn (fields) (map (fn (k) (list k (get fields k))) (keys fields)))) (define data->block (fn (d) (let ((type (get d :type)) (id (get d :id)) (fields (get d :fields))) (cond ((= type "section") (mk-section id (map data->block (get fields "children")))) ((= type "table") (mk-table id (get fields "headers") (get fields "rows"))) (else (mk-block type id (content/-field-pairs fields))))))) (define content/-meta-of (fn (data) (let ((m1 (if (= (get data :title) nil) {} (assoc {} :title (get data :title))))) (let ((m2 (if (= (get data :slug) nil) m1 (assoc m1 :slug (get data :slug))))) (let ((tags (get data :tags))) (if (or (= tags nil) (= (len tags) 0)) m2 (assoc m2 :tags tags))))))) (define content/from-data (fn (data) (doc-with-meta (doc-new (get data :id) (map data->block (get data :blocks))) (content/-meta-of data))))