with builtins; let indentLine = i: s: if i == 0 then s + "\r\n" else " " + indentLine (i - 1) s; escapeText = replaceStrings [ "&" "<" ">" "'" "\"" ] [ "&" "<" ">" "'" """ ]; concat = concatStringsSep ""; none = i: ""; opt = t: e: if t then e else none; many = ee: i: concat (map (e: e i) ee); attr = n: v: i: " " + n + "=\"" + escapeText v + "\""; elem = etype: aa: body: i: let head = "<" + etype + concat (map (a: a i) aa); starttag = head + ">"; endtag = ""; onlytag = head + "/>"; in if isNull body then indentLine i onlytag else if isString body then if body == "" then indentLine i onlytag else indentLine i (starttag + escapeText body + endtag) else if isList body then let contents = many body (i + 1); in if contents == "" then indentLine i onlytag else indentLine i starttag + contents + indentLine i endtag else throw ("expected null, text, or list; found " + builtins.typeOf body) ; toText = e: e 0; in { inherit none opt many attr elem toText; }