racket/collects/xml/private/xexpr.ss
Jay McCarthy d3f6fed328 Error messages
svn: r13321
2009-01-29 20:21:21 +00:00

232 lines
8.7 KiB
Scheme

(module xexpr mzscheme
(require mzlib/unitsig
mzlib/list
scheme/contract
scheme/pretty
mzlib/etc)
(require "sig.ss")
(provide xexpr@)
(define xexpr@
(unit/sig extra-xexpr^
(import xml-structs^ writer^)
;; Xexpr ::= String
;; | (list* Symbol (listof Attribute-srep) (listof Xexpr))
;; | (cons Symbol (listof Xexpr))
;; | Symbol
;; | Nat
;; | Comment
;; | Processing-instruction
;; | Cdata
;; Attribute-srep ::= (list Symbol String)
;; sorting is no longer necessary, since xt3d uses xml->zxexpr, which sorts.
;; assoc-sort : (listof (list Symbol a)) -> (listof (list Symbol a))
(define (assoc-sort to-sort)
(sort to-sort (bcompose string<? (compose symbol->string car))))
(define xexpr-drop-empty-attributes (make-parameter #f))
(define xexpr/c
(make-proj-contract
'xexpr?
(lambda (pos neg src-info name)
(lambda (val)
(with-handlers ([exn:invalid-xexpr?
(lambda (exn)
(raise-contract-error
val
src-info
pos
name
"Not an Xexpr. ~a~n~nContext:~n~a"
(exn-message exn)
(pretty-format val)))])
(validate-xexpr val)
val)))
(lambda (v) #t)))
(define (xexpr? x)
(correct-xexpr? x (lambda () #t) (lambda (exn) #f)))
(define (validate-xexpr x)
(correct-xexpr? x (lambda () #t) (lambda (exn) (raise exn))))
;; ;; ;; ;; ;; ;; ;
;; ; xexpr? helpers
(define-struct (exn:invalid-xexpr exn:fail) (code))
;; correct-xexpr? : any (-> a) (exn -> a) -> a
(define (correct-xexpr? x true false)
(cond
((string? x) (true))
((symbol? x) (true))
((exact-nonnegative-integer? x) (true))
((comment? x) (true))
((pi? x) (true))
((cdata? x) (true))
((list? x)
(or (null? x)
(if (symbol? (car x))
(if (has-attribute? x)
(and (attribute-pairs? (cadr x) true false)
(andmap (lambda (part)
(correct-xexpr? part true false))
(cddr x))
(true))
(andmap (lambda (part)
(correct-xexpr? part true false))
(cdr x)))
(false (make-exn:invalid-xexpr
(format
"Expected a symbol as the element name, given ~s"
(car x))
(current-continuation-marks)
x)))))
(else (false
(make-exn:invalid-xexpr
(format (string-append
"Expected a string, symbol, number, comment, "
"processing instruction, or list, given ~s")
x)
(current-continuation-marks)
x)))))
;; has-attribute? : List -> Boolean
;; True if the Xexpr provided has an attribute list.
(define (has-attribute? x)
(and (> (length x) 1)
(list? (cadr x))
(andmap (lambda (attr)
(pair? attr))
(cadr x))))
;; attribute-pairs? : List (-> a) (exn -> a) -> a
;; True if the list is a list of pairs.
(define (attribute-pairs? attrs true false)
(if (null? attrs)
(true)
(let ((attr (car attrs)))
(if (pair? attr)
(and (attribute-symbol-string? attr true false)
(attribute-pairs? (cdr attrs) true false )
(true))
(false
(make-exn:invalid-xexpr
(format "Expected a pair, given ~a" attr)
(current-continuation-marks)
attr))))))
;; attribute-symbol-string? : List (-> a) (exn -> a) -> a
;; True if the list is a list of String,Symbol pairs.
(define (attribute-symbol-string? attr true false)
(if (symbol? (car attr))
(if (string? (cadr attr))
(true)
(false (make-exn:invalid-xexpr
(format "Expected a string, given ~a" (cadr attr))
(current-continuation-marks)
(cadr attr))))
(false (make-exn:invalid-xexpr
(format "Expected a symbol, given ~a" (car attr))
(current-continuation-marks)
(cadr attr)))))
;; ; end xexpr? helpers
;; ;; ;; ;; ;; ;; ;; ;;
; : (a -> bool) tst -> bool
; To check if l is a (listof p?)
; Don't use (and (list? l) (andmap p? l)) because l may be improper.
(define (listof? p? l)
(let listof-p? ([l l])
(or (null? l)
(and (cons? l) (p? (car l)) (listof-p? (cdr l))))))
; : tst -> bool
(define (xexpr-attribute? b)
(and (pair? b)
(symbol? (car b))
(pair? (cdr b))
(string? (cadr b))
(null? (cddr b))))
; permissive? : parameter bool
(define permissive? (make-parameter #f))
;; xml->xexpr : Content -> Xexpr
(define (xml->xexpr x)
(let* ([non-dropping-combine
(lambda (atts body)
(cons (assoc-sort (map attribute->srep atts))
body))]
[combine (if (xexpr-drop-empty-attributes)
(lambda (atts body)
(if (null? atts)
body
(non-dropping-combine atts body)))
non-dropping-combine)])
(let loop ([x x])
(cond
[(element? x)
(let ([body (map loop (element-content x))]
[atts (element-attributes x)])
(cons (element-name x) (combine atts body)))]
[(pcdata? x) (pcdata-string x)]
[(entity? x) (entity-text x)]
[(or (comment? x) (pi? x) (cdata? x)) x]
[(document? x) (error 'xml->xexpr "Expected content, given ~e\nUse document-element to extract the content." x)]
[(permissive?) x]
[else (error 'xml->xexpr "Expected content, given ~e" x)]))))
;; attribute->srep : Attribute -> Attribute-srep
(define (attribute->srep a)
(list (attribute-name a) (attribute-value a)))
;; srep->attribute : Attribute-srep -> Attribute
(define (srep->attribute a)
(unless (and (pair? a) (pair? (cdr a)) (null? (cddr a)) (symbol? (car a)) (string? (cadr a)))
(error 'srep->attribute "expected (list Symbol String) given ~e" a))
(make-attribute 'scheme 'scheme (car a) (cadr a)))
;; xexpr->xml : Xexpr -> Content
;; The contract is enforced.
(define (xexpr->xml x)
(cond
[(pair? x)
(let ([f (lambda (atts body)
(unless (list? body)
(error 'xexpr->xml
"expected a list of xexprs for the body in ~e"
x))
(make-element 'scheme 'scheme (car x)
atts
(map xexpr->xml body)))])
(if (and (pair? (cdr x))
(or (null? (cadr x))
(and (pair? (cadr x)) (pair? (caadr x)))))
(f (map srep->attribute (cadr x)) (cddr x))
(f null (cdr x))))]
[(string? x) (make-pcdata 'scheme 'scheme x)]
[(or (symbol? x) (exact-nonnegative-integer? x))
(make-entity 'scheme 'scheme x)]
[(or (comment? x) (pi? x) (cdata? x)) x]
[else ;(error 'xexpr->xml "malformed xexpr ~e" x)
x]))
;; xexpr->string : Xexpression -> String
(define (xexpr->string xexpr)
(let ([port (open-output-string)])
(write-xml/content (xexpr->xml xexpr) port)
(get-output-string port)))
;; bcompose : (a a -> c) (b -> a) -> (b b -> c)
(define (bcompose f g)
(lambda (x y) (f (g x) (g y)))))))