(.using [library [lux {"-" symbol} [abstract [monad {"+" do}] [equivalence {"+" Equivalence}] [codec {"+" Codec}]] [control [try {"+" Try}] ["<>" parser ("[1]#[0]" monad) ["<[0]>" text {"+" Parser Slice}]]] [data ["[0]" product] ["[0]" text {"+" \n} ("[1]#[0]" equivalence monoid)] [collection ["[0]" list ("[1]#[0]" functor)] ["[0]" dictionary {"+" Dictionary}]]] [math [number ["n" nat] ["[0]" int]]] [meta ["[0]" symbol ("[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) ($_ <>.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"))] (<| (# ! 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 ($_ text#composite "<>&" text.double_quote)) xml_escape_char^)) (def: xml_identifier (Parser Text) (.slice ($_ .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) (<| (# <>.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 ($_ 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^ ($_ <>.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}))]] ($_ <>.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 _ ($_ text#composite namespace ..namespace_separator name))) (def: .public attribute (-> Attribute Text) ..tag) (def: xml_header Text (let [quote (is (-> Text Text) (function (_ value) ($_ text#composite text.double_quote value text.double_quote)))] ($_ text#composite ""))) (implementation: .public codec (Codec Text XML) (def: encoded (let [attributes (is (-> Attrs Text) (function (_ attrs) (|> attrs dictionary.entries (list#each (function (_ [key value]) ($_ text#composite (..attribute key) "=" text.double_quote (sanitize_value value) text.double_quote))) (text.interposed " "))))] (function (_ input) ($_ 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) "" ($_ text#composite " " (attributes xml_attrs)))] ($_ 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) "" ($_ text#composite " " (attributes xml_attrs)))] (if (list.empty? xml_children) ($_ text#composite prefix "<" tag attrs "/>") ($_ 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^))) (implementation: .public equivalence (Equivalence XML) (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) (# (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)))