open Markup type element = { namespace : string; local_name : string; attributes : (string * string) list; children : (element, string) Either.t list; } (** [tree s] is an [element option] representing the XML element inside of stream [s], if [s] is a complete element from start to end. *) let tree s : element option = let element (namespace, name) attributes children = Either.Left { namespace; local_name=name; attributes=List.filter_map (fun ((ns, name), content) -> (* remove xmlns -- we don't need it. *) match ns with | "http://www.w3.org/2000/xmlns/" -> None | _ -> Some (name, content)) attributes; children; } and text ss = Either.Right (String.concat "" ss) in let opt_el = tree ~text ~element s in Option.bind opt_el (Either.fold ~left:Option.some ~right:(fun _ -> None)) (** [element_to_string element] is a string representation of the underlying XML in [element], for debugging purposes. Note this isn't serialization: namely, XML namespaces are inferred and don't exist in the actual [element]. *) let element_to_string ?(indent = 2) (el : element) = let rec element_to_string parent {local_name; attributes; children; namespace} = let attributes = (if parent == namespace then "" else " xmlns=\"" ^ namespace ^ "\"") ^ String.concat "" (List.map (fun (n, a) -> " " ^ n ^ "=\"" ^ a ^ "\"") attributes) in let tab s = String.split_on_char '\n' s |> String.concat ("\n" ^ String.make indent ' ') in let tag = "\n<" ^ local_name ^ attributes in tag ^ match children with | [] -> "/>" | _ -> ">" ^ (children |> List.map (Either.fold ~left:(element_to_string namespace) ~right:(fun x -> "\n" ^ x)) |> String.concat "" |> tab) ^ "\n" in element_to_string "" el |> String.trim