import Foundation struct XMLElement: Codable, Equatable, CustomStringConvertible { let name: String let xmlns: String? let attributes: [String: String] let content: String? let nodes: [XMLElement] var woClose: Bool = false var stringRepresentation: String { var result = "<\(name)" for (key, value) in attributes { let val = value.escaped result += " \(key)='\(val)'" } if let xmlns { result += " xmlns='\(xmlns)'" } if !nodes.isEmpty { result += ">" for child in nodes { result += child.stringRepresentation } result += "" } else { if let content = content { result += ">" result += content result += "" } else { result += woClose ? ">" : "/>" } } return result } var description: String { stringRepresentation.prettyStr } var data: Data { Data(stringRepresentation.utf8) } } // I decided to not use mutation functions and just // make a new element during update extension XMLElement { func updateXmlns(_ new: String?) -> XMLElement { XMLElement( name: name, xmlns: new, attributes: attributes, content: content, nodes: nodes, woClose: woClose ) } func addNode(_ new: XMLElement) -> XMLElement { var nodes = nodes nodes.append(new) return XMLElement( name: name, xmlns: xmlns, attributes: attributes, content: content, nodes: nodes, woClose: woClose ) } func updateContent(_ new: String?) -> XMLElement { guard let new else { return self } var content = self.content ?? "" content += new return XMLElement( name: name, xmlns: xmlns, attributes: attributes, content: content, nodes: nodes, woClose: woClose ) } } // extension XMLElement { static var randomId: String { let letters = "abcdefghijklmnopqrstuvwxyz0123456789" return String((0 ..< 8).map { _ in letters.randomElement() ?? "x" }) } }