// xml - XML builder for Lux // // Provides a simple API for constructing well-formed XML documents. // Designed for generating RSS feeds, sitemaps, and other XML output. // An XML attribute (key-value pair) type Attr = | Attr(String, String) // An XML node: element with tag, attributes, and children, or text content type Node = | Element(String, List, List) | Text(String) | CData(String) | Raw(String) // Escape special XML characters in text content pub fn escape(s: String): String = String.replace( String.replace( String.replace( String.replace( String.replace(s, "&", "&"), "<", "<"), ">", ">"), "\"", """), "'", "'") // Create a text node pub fn text(content: String): Node = Text(content) // Create a CDATA section pub fn cdata(content: String): Node = CData(content) // Create a raw (unescaped) node pub fn raw(content: String): Node = Raw(content) // Create an element with tag, attributes, and children pub fn element(tag: String, attrs: List, children: List): Node = Element(tag, attrs, children) // Create an element with no attributes pub fn el(tag: String, children: List): Node = Element(tag, [], children) // Create a self-closing element with attributes and no children pub fn selfClosing(tag: String, attrs: List): Node = Element(tag, attrs, []) // Create an attribute pub fn attr(key: String, value: String): Attr = Attr(key, value) // Create an element with text content pub fn textEl(tag: String, content: String): Node = Element(tag, [], [Text(content)]) // Create an element with attributes and text content pub fn textElAttr(tag: String, attrs: List, content: String): Node = Element(tag, attrs, [Text(content)]) // Render a single attribute to string fn renderAttr(a: Attr): String = match a { Attr(k, v) => " " + k + "=\"" + escape(v) + "\"", } // Render a list of attributes to string fn renderAttrs(attrs: List): String = match List.head(attrs) { None => "", Some(a) => renderAttr(a) + match List.tail(attrs) { Some(rest) => renderAttrs(rest), None => "", }, } // Render child nodes to string fn renderChildren(nodes: List): String = match List.head(nodes) { None => "", Some(node) => render(node) + match List.tail(nodes) { Some(rest) => renderChildren(rest), None => "", }, } // Render a node to an XML string pub fn render(node: Node): String = match node { Element(tag, attrs, children) => { let attrStr = renderAttrs(attrs) if List.length(children) == 0 then "<" + tag + attrStr + "/>" else "<" + tag + attrStr + ">" + renderChildren(children) + "" }, Text(content) => escape(content), CData(content) => "", Raw(content) => content, } // Render a complete XML document with declaration pub fn document(root: Node): String = "\n" + render(root) // Render with a custom declaration (e.g., for RSS stylesheets) pub fn documentWithDecl(decl: String, root: Node): String = decl + "\n" + render(root)