Super skinny XML document generation with F#
I needed to generate some simple XML documents, in memory, from some F# script. From my C# days I was already familiar with the
System.Xml.Linq namespace, which I still quite like. But it wasn’t particularly clean to use from F#. So I wrote a really simple F# wrapper for some of its most commonly used features.
XmlToolkit for F#
module nbevans.Util.XmlToolkit open System.Text open System.Xml open System.Xml.Linq open System.IO let XDeclaration version encoding standalone = XDeclaration(version, encoding, standalone) let XLocalName localName namespaceName = XName.Get(localName, namespaceName) let XName expandedName = XName.Get(expandedName) let XDocument xdecl content = XDocument(xdecl, content |> Seq.map (fun v -> v :> obj) |> Seq.toArray) let XComment (value:string) = XComment(value) :> obj let XElementNS localName namespaceName content = XElement(XLocalName localName namespaceName, content |> Seq.map (fun v -> v :> obj) |> Seq.toArray) :> obj let XElement expandedName content = XElement(XName expandedName, content |> Seq.map (fun v -> v :> obj) |> Seq.toArray) :> obj let XAttributeNS localName namespaceName value = XAttribute(XLocalName localName namespaceName, value) :> obj let XAttribute expandedName value = XAttribute(XName expandedName, value) :> obj type XDocument with /// Saves the XML document to a MemoryStream using UTF-8 encoding, indentation and character checking. member doc.Save() = let ms = new MemoryStream() use xtw = XmlWriter.Create(ms, XmlWriterSettings(Encoding = Encoding.UTF8, Indent = true, CheckCharacters = true)) doc.Save(xtw) ms.Position <- 0L ms
The principle of this module is that it overrides the key type names like
System.Xml.Linq.XElement and the others with F# functions that effectively provide the equivalent constructor behaviour but in a more functional signature. Then a
XDocument type extension adds a useful
Save() function (since the stock ones are so useless on their own). … and here is an example usage straight from my app (but hopefully you will get the idea):
let doc = XDocument (XDeclaration "1.0" "UTF-8" "yes") [ XComment "This document was automatically generated by a configuration script." XElement "Metadata" [ XElement "SystemMetadata" [ XElement "ScannedBy" ["PCT"] XElement "GenerationDate" [DateTime.UtcNow.ToString("s")] XElement "IndexedBy" ["UNKNOWN"] XElement "IndexedOn" ["UNKNOWN"] XElement "FileName" [createPackageContentFileName cp.Id fileName] XElement "ScanInfo" [ XElement "NumberOfPagesScanned" [string formPdfPageCount] XElement "IpAddress" ["UNKNOWN"] XElement "MachineName" ["UNKNOWN"] XElement "NumberOfBlankPages" ["0"] ] ] XElement "UserDefinedMetadata" [ XElement "Address1" [defaultArg (gp.Fields.TryFind "property-address") "UNKNOWN"] XElement "Postcode" [defaultArg (gp.Fields.TryFind "property-postcode") "UNKNOWN"] XElement "Patchcode" ["1"] XElement "Reviewdate" [DateTime.UtcNow.AddYears(1).ToString("s")] ] ] ] let ms = doc.Save() // 'ms' at this point contains a System.IO.MemoryStream of the generated XML document. // That was my use-case, but maybe you will want to adapt this code or the XmlToolkit module itself to use a different type of stream; perhaps a FileStream. I'm a firm believer in K.I.S.S to avoid over-engineering.