(.require [library [lux (.except symbol) [abstract [monad (.only do)] [equivalence (.only Equivalence)] [codec (.only Codec)]] [control [try (.only Try)] ["<>" parser (.use "[1]#[0]" monad)]] [data ["[0]" product] ["[0]" text (.only \n) (.use "[1]#[0]" equivalence monoid) ["<[1]>" \\parser (.only Parser Slice)]] [collection ["[0]" list (.use "[1]#[0]" functor)] ["[0]" dictionary (.only Dictionary)]]] [math [number ["n" nat] ["[0]" int]]] [meta ["[0]" symbol (.use "[1]#[0]" equivalence codec)]]]]) (type .public Tag Symbol) (type .public Attribute Symbol) (type .public Attrs (Dictionary Attribute Text)) (def .public attributes Attrs (dictionary.empty symbol.hash)) (type .public XML (Rec XML (Variant {#Text Text} {#Node Tag Attrs (List XML)}))) (def namespace_separator ":") (def xml_standard_escape_char^ (Parser Text) (all <>.either (<>.after (.this "<") (<>#in "<")) (<>.after (.this ">") (<>#in ">")) (<>.after (.this "&") (<>#in "&")) (<>.after (.this "'") (<>#in "'")) (<>.after (.this """) (<>#in text.double_quote)) )) (def xml_unicode_escape_char^ (Parser Text) (|> (do [! <>.monad] [hex? (<>.maybe (.this "x"))] (<| (at ! each (|>> .nat text.of_char)) (<>.codec int.decimal) .slice .many! (case hex? {.#None} .decimal! {.#Some _} .hexadecimal!))) (<>.before (.this ";")) (<>.after (.this "&#")))) (def xml_escape_char^ (Parser Text) (<>.either xml_standard_escape_char^ xml_unicode_escape_char^)) (def xml_char^ (Parser Text) (<>.either (.none_of (all text#composite "<>&" text.double_quote)) xml_escape_char^)) (def xml_identifier (Parser Text) (.slice (all .and! (<>.either (.one_of! "_") .alpha!) (.some! (<>.either (.one_of! "_.-") .alpha_num!))))) (def namespaced_symbol^ (Parser Symbol) (do <>.monad [first_part xml_identifier ?second_part (<| <>.maybe (<>.after (.this ..namespace_separator)) xml_identifier)] (case ?second_part {.#None} (in ["" first_part]) {.#Some second_part} (in [first_part second_part])))) (def tag^ namespaced_symbol^) (def attr_name^ namespaced_symbol^) (def spaced^ (All (_ a) (-> (Parser a) (Parser a))) (let [white_space^ (<>.some .space)] (|>> (<>.before white_space^) (<>.after white_space^)))) (def attr_value^ (Parser Text) (let [value^ (.some xml_char^)] (<>.either (.enclosed [text.double_quote text.double_quote] value^) (.enclosed ["'" "'"] value^)))) (def attrs^ (Parser Attrs) (<| (at <>.monad each (dictionary.of_list symbol.hash)) <>.some (<>.and (..spaced^ attr_name^)) (<>.after (.this "=")) (..spaced^ attr_value^))) (def (close_tag^ expected) (-> Tag (Parser [])) (do <>.monad [actual (|> tag^ ..spaced^ (<>.after (.this "/")) (.enclosed ["<" ">"]))] (<>.assertion (all text#composite "Close tag does not match open tag." \n "Expected: " (symbol#encoded expected) \n " Actual: " (symbol#encoded actual) \n) (symbol#= expected actual)))) (def comment^ (Parser Slice) (|> (.not! (.this "--")) .some! (.enclosed [""]) ..spaced^)) (def xml_header^ (Parser Attrs) (|> (..spaced^ attrs^) (<>.before (.this "?>")) (<>.after (.this ".this "]]>")] (|> (.some! (.not! end)) (<>.after end) (<>.after (.this " (..spaced^ (.many xml_char^)) (<>.either (.slice cdata^)) (<>#each (|>> {#Text})))) (def null^ (Parser Any) (.this (text.of_char 0))) (def xml^ (Parser XML) (|> (<>.rec (function (_ node^) (|> (do <>.monad [_ (.this "<") tag (..spaced^ tag^) attrs (..spaced^ attrs^) .let [no_children^ (all <>.either (do <>.monad [_ (.this "/>")] (in {#Node tag attrs (list)})) (do <>.monad [_ (.this ">") _ (<>.some (<>.either .space! ..comment^)) _ (..close_tag^ tag)] (in {#Node tag attrs (list)}))) with_children^ (do <>.monad [_ (.this ">") children (<>.many node^) _ (..close_tag^ tag)] (in {#Node tag attrs children}))]] (all <>.either no_children^ with_children^)) ..spaced^ (<>.before (<>.some ..comment^)) (<>.after (<>.some ..comment^)) (<>.either ..text^)))) (<>.before (<>.some ..null^)) (<>.after (<>.maybe ..xml_header^)))) (def (sanitize_value input) (-> Text Text) (|> input (text.replaced "&" "&") (text.replaced "<" "<") (text.replaced ">" ">") (text.replaced "'" "'") (text.replaced text.double_quote """))) (def .public (tag [namespace name]) (-> Tag Text) (case namespace "" name _ (all text#composite namespace ..namespace_separator name))) (def .public attribute (-> Attribute Text) ..tag) (def xml_header Text (let [quote (is (-> Text Text) (function (_ value) (all text#composite text.double_quote value text.double_quote)))] (all text#composite ""))) (def .public codec (Codec Text XML) (implementation (def encoded (let [attributes (is (-> Attrs Text) (function (_ attrs) (|> attrs dictionary.entries (list#each (function (_ [key value]) (all text#composite (..attribute key) "=" text.double_quote (sanitize_value value) text.double_quote))) (text.interposed " "))))] (function (_ input) (all text#composite ..xml_header text.new_line (loop (again [prefix "" input input]) (case input {#Text value} (sanitize_value value) (pattern {#Node xml_tag xml_attrs (list {#Text value})}) (let [tag (..tag xml_tag) attrs (if (dictionary.empty? xml_attrs) "" (all text#composite " " (attributes xml_attrs)))] (all text#composite prefix "<" tag attrs ">" (sanitize_value value) "")) {#Node xml_tag xml_attrs xml_children} (let [tag (..tag xml_tag) attrs (if (dictionary.empty? xml_attrs) "" (all text#composite " " (attributes xml_attrs)))] (if (list.empty? xml_children) (all text#composite prefix "<" tag attrs "/>") (all text#composite prefix "<" tag attrs ">" (|> xml_children (list#each (|>> (again (text#composite prefix text.tab)) (text#composite text.new_line))) text.together) text.new_line prefix ""))))) )))) (def decoded (.result ..xml^)))) (def .public equivalence (Equivalence XML) (implementation (def (= reference sample) (case [reference sample] [{#Text reference/value} {#Text sample/value}] (text#= reference/value sample/value) [{#Node reference/tag reference/attrs reference/children} {#Node sample/tag sample/attrs sample/children}] (and (symbol#= reference/tag sample/tag) (at (dictionary.equivalence text.equivalence) = reference/attrs sample/attrs) (n.= (list.size reference/children) (list.size sample/children)) (|> (list.zipped_2 reference/children sample/children) (list.every? (product.uncurried =)))) _ false))))