This time I will create a word document (open xml) in F# without using the Open XML SDK and using XML libraries. Things have been intentionally kept simple.
I start by modelling a simple word document:
There are Two types of paragraphs: normal and bold ones:
- //reference to the package
- #r "WindowsBase"
- open System
- open System.IO
- open System.IO.Packaging
- type Paragraph =
- | Normal of string
- | Bold of string
And a document is a list of paragraphs:
- type Document =
- | Paragraphs of Paragraph list
This is the test:
- //test
- let list = [Normal("test1"); Normal("test2"); Bold("test3"); Normal("test4")]
- let doc = Paragraphs(list)
Next we have the to create an xml version of document:
- //helpers
- // http://weblogs.sqlteam.com/mladenp/archive/2008/10/21/Different-ways-how-to-escape-an-XML-string-in-C.aspx
- let encodedXml (text: string) =
- text.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """).Replace("'", "'")
- let xml1 = "<node>it's my \"node\" & i like it<node>"
- let encode = encodedXml xml1
- let addString acc item = acc + "\r\t" + item
- let createParagraphNormal text =
- "<w:p>
- <w:r>
- <w:t>" + encodedXml text +
- "</w:t>
- </w:r>
- </w:p>"
- let createParagraphBold text =
- "<w:p>
- <w:r>
- <w:rPr>
- <w:b />
- </w:rPr>
- <w:t>" + encodedXml text +
- "</w:t>
- </w:r>
- </w:p>"
- let createParagraph = function
- | Normal(text) -> createParagraphNormal text
- | Bold (text) -> createParagraphBold text
- let createParagraphs = function
- | Paragraphs(list) -> list|> List.map createParagraph |> List.reduce addString
- //test
- let p = createParagraphs doc
- let createDocument doc =
- let startDocument = @"<?xml version=""1.0"" encoding=""utf-8""?>
- <w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
- <w:body>"
- let endDocument = " </w:body>
- </w:document>"
- let content = createParagraphs doc
- startDocument + content + endDocument
Then we have to create a package, add the document and save it to disk:
- //add the document to package and save
- let createFile doc (fileName:string) =
- using (Package.Open(fileName, FileMode.Create, FileAccess.ReadWrite))(fun package ->
- let uri = new Uri("/word/document.xml", UriKind.Relative )
- let partDocumentXML =
- package.CreatePart(
- uri,
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" )
- using(new StreamWriter(partDocumentXML.GetStream(FileMode.Create, FileAccess.Write)))(fun stream ->
- doc |> createDocument |> stream.Write
- )
- package.CreateRelationship(
- uri,
- TargetMode.Internal,
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
- "rId1") |> ignore
- )
- //test
- let fileName = @"D:\Tmp\test.docx"
- createFile doc fileName;;
This is the result:
I have created a map from twitter to my document model
- type UserStatus =
- { UserName : string;
- ProfileImage : string;
- Status : string;
- StatusDate : DateTime }
- let mapUserStatus (us:UserStatus) =
- let date = "Date: " + us.StatusDate.ToShortDateString() + " time: " + us.StatusDate.ToLongTimeString()
- [Normal(date); Bold(us.UserName); Normal(us.Status); Normal("")]
- let addUserStatus acc item = item @ acc
- let result = xml|> parseTweetXml |> List.map mapUserStatus|> List.reduce addUserStatus
(the details of the twitter part can be found at: http://blogs.msdn.com/b/dsyme/archive/2010/01/10/async-and-parallel-design-patterns-in-f-reporting-progress-with-events-plus-twitter-sample.aspx)
This results in the following document:
Remark: I had to Google “Weiner” to understand the jokes.