/racket-5-0-2-bin-i386-osx-mac-dmg/collects/xml/private/writer.rkt

http://github.com/smorin/f4f.arc · Racket · 169 lines · 122 code · 25 blank · 22 comment · 28 complexity · 4610d8f4d4b354fd0cf58344c84d47ad MD5 · raw file

  1. #lang racket
  2. (require "structures.rkt")
  3. (provide/contract
  4. [write-xml ((document?) (output-port?) . ->* . void?)]
  5. [display-xml ((document?) (output-port?) . ->* . void?)]
  6. [write-xml/content ((content/c) (output-port?) . ->* . void?)]
  7. [display-xml/content ((content/c) (output-port?) . ->* . void?)]
  8. [empty-tag-shorthand (parameter/c (or/c (symbols 'always 'never) (listof symbol?)))]
  9. [html-empty-tags (listof symbol?)])
  10. ;; (empty-tag-shorthand) : (U 'always 'never (listof Symbol))
  11. (define empty-tag-shorthand
  12. (make-parameter 'always
  13. (lambda (x)
  14. (if (or (eq? x 'always) (eq? x 'never) (and (list? x) (andmap symbol? x)))
  15. x
  16. (error 'empty-tag-shorthand "expected 'always, 'never, or a list of symbols: received ~e" x)))))
  17. (define html-empty-tags '(param meta link isindex input img hr frame col br basefont base area))
  18. ;; gen-write/display-xml/content : (Nat Output-port -> Void) -> Content [Output-Port]-> Void
  19. (define (gen-write/display-xml/content dent)
  20. (lambda (c [out (current-output-port)]) (write-xml-content c 0 dent out)))
  21. ;; indent : Nat Output-port -> Void
  22. (define (indent n out)
  23. (newline out)
  24. (let loop ([n n])
  25. (unless (zero? n)
  26. (display #\space out)
  27. (loop (sub1 n)))))
  28. ;; write-xml/content : Content [Output-port] -> Void
  29. (define write-xml/content (gen-write/display-xml/content void))
  30. ;; display-xml/content : Content [Output-port] -> Void
  31. (define display-xml/content (gen-write/display-xml/content indent))
  32. ;; gen-write/display-xml : (Content [Output-port] -> Void) -> Document [Output-port] -> Void
  33. (define (gen-write/display-xml output-content)
  34. (lambda (doc [out (current-output-port)])
  35. (let ([prolog (document-prolog doc)])
  36. (display-outside-misc (prolog-misc prolog) out)
  37. (display-dtd (prolog-dtd prolog) out)
  38. (display-outside-misc (prolog-misc2 prolog) out))
  39. (output-content (document-element doc) out)
  40. (display-outside-misc (document-misc doc) out)))
  41. ; display-dtd : document-type oport -> void
  42. (define (display-dtd dtd out)
  43. (when dtd
  44. (fprintf out "<!DOCTYPE ~a" (document-type-name dtd))
  45. (let ([external (document-type-external dtd)])
  46. (cond
  47. [(external-dtd/public? external)
  48. (fprintf out " PUBLIC \"~a\" \"~a\""
  49. (external-dtd/public-public external)
  50. (external-dtd-system external))]
  51. [(external-dtd/system? external)
  52. (fprintf out " SYSTEM \"~a\"" (external-dtd-system external))]
  53. [(not external) (void)]))
  54. (display ">" out)
  55. (newline out)))
  56. ;; write-xml : Document [Output-port] -> Void
  57. (define write-xml (gen-write/display-xml write-xml/content))
  58. ;; display-xml : Document [Output-port] -> Void
  59. (define display-xml (gen-write/display-xml display-xml/content))
  60. ;; display-outside-misc : (listof Misc) Output-port -> Void
  61. (define (display-outside-misc misc out)
  62. (for-each (lambda (x)
  63. ((cond
  64. [(comment? x) write-xml-comment]
  65. [(p-i? x) write-xml-p-i]) x 0 void out)
  66. (newline out))
  67. misc))
  68. ;; write-xml-content : Content Nat (Nat Output-Stream -> Void) Output-Stream -> Void
  69. (define (write-xml-content el over dent out)
  70. ((cond
  71. [(element? el) write-xml-element]
  72. [(pcdata? el) write-xml-pcdata]
  73. [(cdata? el) write-xml-cdata]
  74. [(entity? el) write-xml-entity]
  75. [(comment? el) write-xml-comment]
  76. [(p-i? el) write-xml-p-i]
  77. [else (error 'write-xml-content "received ~e" el)])
  78. el over dent out))
  79. ;; write-xml-element : Element Nat (Nat Output-Stream -> Void) Output-Stream -> Void
  80. (define (write-xml-element el over dent out)
  81. (let* ([name (element-name el)]
  82. [start (lambda (str)
  83. (write-xml-base str over dent out)
  84. (display name out))]
  85. [content (element-content el)])
  86. (start "<")
  87. (for ([att (in-list (element-attributes el))])
  88. (fprintf out " ~a=\"~a\"" (attribute-name att)
  89. (escape (attribute-value att) escape-attribute-table)))
  90. (if (and (null? content)
  91. (let ([short (empty-tag-shorthand)])
  92. (case short
  93. [(always) #t]
  94. [(never) #f]
  95. [else (memq (lowercase-symbol name) short)])))
  96. (display " />" out)
  97. (begin
  98. (display ">" out)
  99. (for ([c (in-list content)])
  100. (write-xml-content c (incr over) dent out))
  101. (start "</")
  102. (display ">" out)))))
  103. ; : sym -> sym
  104. (define lowercases (make-weak-hash))
  105. (define (lowercase-symbol x)
  106. (or (hash-ref lowercases x #f)
  107. (let ([s (symbol->string x)])
  108. (let ([s (string->symbol (string-downcase s))])
  109. (hash-set! lowercases x s)
  110. s))))
  111. ;; write-xml-base : (U String Char Symbol) Nat (Nat Output-Stream -> Void) Output-Stream -> Void
  112. (define (write-xml-base el over dent out)
  113. (dent over out)
  114. (display el out))
  115. ;; write-xml-pcdata : Pcdata Nat (Nat Output-Stream -> Void) Output-Stream -> Void
  116. (define (write-xml-pcdata str over dent out)
  117. (write-xml-base (escape (pcdata-string str) escape-table) over dent out))
  118. ;; write-xml-cdata : Cdata Nat (Nat Output-Stream -> Void) Output-Stream -> Void
  119. (define (write-xml-cdata cdata over dent out)
  120. ;; XXX: Different kind of quote is needed, for assume the user includes the <![CDATA[...]]> with proper quoting
  121. (write-xml-base (format "~a" (cdata-string cdata)) over dent out))
  122. ;; write-xml-p-i : Processing-instruction Nat (Nat Output-Stream -> Void) Output-Stream -> Void
  123. (define (write-xml-p-i p-i over dent out)
  124. (write-xml-base (format "<?~a ~a?>" (p-i-target-name p-i) (p-i-instruction p-i)) over dent out))
  125. ;; write-xml-comment : Comment Nat (Nat Output-Stream -> Void) Output-Stream -> Void
  126. (define (write-xml-comment comment over dent out)
  127. (write-xml-base (format "<!--~a-->" (comment-text comment)) over dent out))
  128. ;; write-xml-entity : Entity Nat (Nat Output-stream -> Void) Output-stream -> Void
  129. (define (write-xml-entity entity over dent out)
  130. (let ([n (entity-text entity)])
  131. (fprintf out (if (number? n) "&#~a;" "&~a;") n)))
  132. (define escape-table #rx"[<>&]")
  133. (define escape-attribute-table #rx"[<>&\"]")
  134. (define (replace-escaped s)
  135. (case (string-ref s 0)
  136. [(#\<) "&lt;"]
  137. [(#\>) "&gt;"]
  138. [(#\&) "&amp;"]
  139. [(#\") "&quot;"]))
  140. ;; escape : String -> String
  141. (define (escape x table)
  142. (regexp-replace* table x replace-escaped))
  143. ;; incr : Nat -> Nat
  144. (define (incr n) (+ n 2))