adam@9: open Parse adam@9: adam@9: con attribute = fn t => {Nam : string, adam@9: Parse : string -> option t} adam@9: adam@9: con tag = fn ts => {Nam : string, adam@9: Attributes : $(map attribute ts), adam@9: Folder : folder ts, adam@9: Construct : ctx ::: {Unit} -> [[Body] ~ ctx] => $ts adam@9: -> xml ([Body] ++ ctx) [] [] -> xml ([Body] ++ ctx) [] []} adam@9: adam@9: fun tag [use] [ignore] [use ~ ignore] (fl : folder use) (name : string) (attrs : $(map attribute use)) adam@9: (construct : ctx ::: {Unit} -> [[Body] ~ ctx] => Basis.tag (use ++ ignore) ([Body] ++ ctx) ([Body] ++ ctx) [] []) = adam@9: {Nam = name, adam@9: Attributes = attrs, adam@9: Folder = fl, adam@9: Construct = fn [ctx] [[Body] ~ ctx] (ats : $use) (inner : xml ([Body] ++ ctx) [] []) => adam@17: Basis.tag None None ats construct inner} adam@9: adam@9: fun simpleTag [ignore] name (bt : bodyTag ignore) : tag [] = adam@9: @@tag [[]] [ignore] ! _ name {} (fn [ctx] [[Body] ~ ctx] => bt ()) adam@9: adam@9: fun simpleTag' [use] [ignore] [use ~ ignore] (fl : folder use) adam@9: name (bt : bodyTag (use ++ ignore)) (ats : $(map attribute use)) : tag use = adam@9: @@tag [use] [ignore] ! fl name ats (fn [ctx] [[Body] ~ ctx] => bt ()) adam@9: adam@9: fun url name = {Nam = name, adam@9: Parse = checkUrl} adam@9: adam@9: datatype error a = adam@9: Good of a adam@9: | Bad of string adam@9: adam@9: fun format [tags] (fl : folder tags) (tags : $(map tag tags)) [ctx] [[Body] ~ ctx] s = adam@9: let adam@9: fun loop s : error (xml ([Body] ++ ctx) [] [] * string) = adam@9: case String.msplit {Haystack = s, Needle = "&<"} of adam@9: None => Good (cdata s, "") adam@9: | Some (pre, ch, post) => adam@9: case ch of adam@9: #"&" => adam@9: (case String.split post #";" of adam@9: None => Bad "No ';' after '&'" adam@9: | Some (code, post) => adam@9: let adam@9: val xml = adam@9: case code of adam@9: "lt" => < adam@9: | "gt" => > adam@9: | "amp" => & adam@9: | _ => adam@9: in adam@9: case loop post of adam@9: Good (after, post) => Good ({[pre]}{xml}{after}, post) adam@9: | x => x adam@9: end) adam@9: | _ => adam@9: if String.length post > 0 && String.sub post 0 = #"/" then adam@9: case String.split post #"\x3E" of adam@9: None => Bad "No '>' after ' Good ({[pre]}, post) adam@9: else adam@9: case String.msplit {Haystack = post, Needle = " >"} of adam@9: None => Bad "No '>' after '<'" adam@9: | Some (tname, ch, post) => adam@9: @foldR [tag] [fn _ => unit -> error (xml ([Body] ++ ctx) [] [] * string)] adam@9: (fn [nm :: Name] [ts :: {Type}] [r :: {{Type}}] [[nm] ~ r] (meta : tag ts) acc () => adam@9: if meta.Nam = tname then adam@9: let adam@9: fun doAttrs (ch, post, ats : $(map option ts)) = adam@9: if String.length post > 0 && Char.isSpace (String.sub post 0) then adam@9: doAttrs (ch, String.substring post {Start = 1, adam@9: Len = String.length post - 1}, adam@9: ats) adam@9: else adam@9: case ch of adam@9: #"\x3E" => Good (ats, post) adam@9: | _ => adam@9: case String.split post #"=" of adam@9: None => adam@9: (case String.split post #"\x3E" of adam@9: None => Bad "No tag ender '\x3E'" adam@9: | Some (_, post) => Good (ats, post)) adam@9: | Some (aname, post) => adam@9: if String.length post >= 1 && String.sub post 0 = #"\"" then adam@9: case String.split (String.substring post adam@9: {Start = 1, adam@9: Len = String.length post adam@9: - 1}) adam@9: #"\"" of adam@9: None => Bad "No '\"' to end attribute value" adam@9: | Some (aval, post) => adam@9: let adam@9: val ats = adam@9: @map2 [attribute] [option] [option] adam@9: (fn [t] meta v => adam@9: if aname = meta.Nam then adam@9: meta.Parse aval adam@9: else adam@9: v) adam@9: meta.Folder meta.Attributes ats adam@9: in adam@9: doAttrs (#" ", post, ats) adam@9: end adam@9: else adam@9: Bad "Attribute value doesn't begin with quote" adam@9: in adam@9: case doAttrs (ch, post, @map0 [option] (fn [t :: Type] => None) adam@9: meta.Folder) of adam@9: Good (ats, post) => adam@9: let adam@9: val ats = adam@18: @map2 [attribute] [option] [ident] adam@9: (fn [t] meta v => adam@9: case v of adam@9: None => error Missing attribute {[meta.Nam]} adam@9: for {[tname]} adam@9: | Some v => v) adam@9: meta.Folder meta.Attributes ats adam@9: in adam@9: case loop post of adam@9: Good (inner, post) => adam@9: (case loop post of adam@9: Good (after, post) => adam@9: Good ({[pre]}{meta.Construct [ctx] ! adam@9: ats inner}{after}, post) adam@9: | x => x) adam@9: | x => x adam@9: end adam@9: | Bad s => Bad s adam@9: end adam@9: else adam@9: acc ()) adam@9: (fn () => Bad ("Unknown HTML tag " ^ tname)) fl tags () adam@9: in adam@9: case loop s of adam@9: Bad msg => Failure msg adam@9: | Good (xml, _) => Success xml adam@9: end adam@9: adam@9: val b = simpleTag "b" @@b adam@9: val i = simpleTag "i" @@i adam@9: val a = simpleTag' "a" @@a {Href = url "href"} adam@9: