#lang racket
(require (prefix-in wt: "parser.ss"))
(require xml)
(require racket/cmdline)
(require (prefix-in srfi19: srfi/19)) (require srfi/8)
(define-syntax expect
(syntax-rules ()
((_ form ...) (void))))
(define-syntax expect-failure
(syntax-rules ()
((_ form) (void))))
(define (write-xml-to-port wiki-text port structured-xml?)
(write-xml/content
(xexpr->xml
`(top ((xmlns "http://ns.nxg.me.uk/squicky/xml")
,@(map (lambda (k)
(cond ((or (eqv? k 'date)
(eqv? k 'updated))
(list k (srfi19:date->string (wt:lookup-parsed wiki-text k) "~4")))
(else (list k (wt:lookup wiki-text k)))))
(wt:lookup-keys wiki-text)))
,@(if structured-xml?
(add-body-structure (wt:body wiki-text))
(wt:body wiki-text))))
port)
(newline port))
(define (add-body-structure body-sexp)
(let loop ((body body-sexp)
(stack '((((class "body"))))))
(define (pop-stack stack)
(when (null? stack)
(error "Can't pop null stack"))
(values (car stack) (cdr stack)))
(define (push-stack stack val)
(cons val stack))
(define (push-onto-stack-top stack val)
(receive (top rest)
(pop-stack stack)
(push-stack rest (cons val top))))
(define (squash-stack stack)
(receive (top rest)
(pop-stack stack)
(push-onto-stack-top rest `(div . ,(reverse top)))))
(define (misc-list->string list-of-string-or-char)
(apply string-append
(map (λ (x)
(cond ((string? x) x)
((char? x) (string x))
((and (pair? x) (symbol? (car x)))
(misc-list->string (cdr x)))
(else (error (format "Can't stringize ~s" x)))))
list-of-string-or-char)))
(define (add-section-at-level new-level title old-stack)
(let ((diff (- new-level (length old-stack))))
(cond ((< diff 0) (add-section-at-level new-level title (squash-stack old-stack)))
((= diff 0) (push-stack (squash-stack old-stack) `(((title ,title)))))
((= diff +1) (push-stack old-stack `(((title ,title)))))
(else (error "Can't go from h2->h4")))))
(if (null? body)
(let clear ((s stack)) (cond ((null? s) (error "Stack can't be null?"))
((null? (cdr s)) `((div . ,(reverse (car s))))) (else
(clear (squash-stack s)))))
(case (caar body)
((h2)
(loop (cdr body)
(add-section-at-level 2 (misc-list->string (cdar body)) stack)))
((h3)
(loop (cdr body)
(add-section-at-level 3 (misc-list->string (cdar body)) stack)))
((h4)
(loop (cdr body)
(add-section-at-level 4 (misc-list->string (cdar body)) stack)))
(else
(loop (cdr body)
(push-onto-stack-top stack (car body))))))))
(provide add-body-structure) (expect ((div ((class "body")) (p "Hello") (p "there"))) (add-body-structure '((p "Hello") (p "there"))))
(expect ((div ((class "body")) (p "Hello") (div ((title "Section")) (p "content") (p "more"))))
(add-body-structure '((p "Hello") (h2 "Section") (p "content") (p "more"))))
(expect ((div ((class "body"))
(div ((title "H2.1"))
(p "t2.1")
(div ((title "H3"))
(div ((title "H4"))
(p "t4")
(p "t4.1"))))
(div ((title "H2.2"))
(p "t2.2.1")
(p "t2.2.2"))))
(add-body-structure '((h2 "H2.1") (p "t2.1") (h3 "H3") (h4 "H4") (p "t4") (p "t4.1") (h2 "H2.2") (p "t2.2.1") (p "t2.2.2"))))
(expect ((div ((class "body")) (div ((title "pre/post€")) (p "title"))))
(add-body-structure '((h2 "pre" #\/ "post" #\€) (p "title"))))
(expect-failure (add-body-structure '((h2 "H2") (p "text") (h4 "H4"))))
(define (Usage)
(display (format "wiki2xml [--output filename] input-file?~%"))
(exit 1))
(define (define-option default)
(let ((v default))
(lambda args
(if (null? args)
v
(set! v (car args))))))
(define (main . args)
(let ((output-file (define-option #f))
(structured-output (define-option #t)))
(let ((input-file
(command-line
#:program "wiki2xml"
#:argv args
#:once-each
("--output"
filename
"File to receive XML output"
(output-file filename))
("--structured"
"Produce output with sections in <div>"
(structured-output #t))
("--nostructured"
"Produce output with sections in <h2>, <h3>, <h4>"
(structured-output #f))
#:args filenames
filenames)))
(when (> (length input-file) 1)
(Usage))
(let ((input-port (if (null? input-file)
(current-input-port)
(open-input-file (car input-file))))
(output-port (cond ((output-file) => open-output-file)
(else (current-output-port)))))
(write-xml-to-port (wt:parse input-port)
output-port
(structured-output))))))
(provide main)