Tuesday, June 7, 2011

Create Open XML Word document in F# (update)

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:
  1. //reference to the package
  2. #r "WindowsBase"
  3.  
  4. open System
  5. open System.IO
  6. open System.IO.Packaging
  7.  
  8. type Paragraph =
  9.     | Normal of string
  10.     | Bold of string
 
And a document is a list of paragraphs:
  1. type Document =
  2.     | Paragraphs of Paragraph list
 
This is the test:
  1. //test
  2. let list = [Normal("test1"); Normal("test2"); Bold("test3"); Normal("test4")]
  3. let doc = Paragraphs(list)

 

Next we have the to create an xml version of document:






  1. //helpers

  2.  

  3. // http://weblogs.sqlteam.com/mladenp/archive/2008/10/21/Different-ways-how-to-escape-an-XML-string-in-C.aspx

  4. let encodedXml (text: string) =

  5.     text.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("\"", "&quot;").Replace("'", "&apos;")

  6.  

  7. let xml1 = "<node>it's my \"node\" & i like it<node>"

  8. let encode = encodedXml xml1

  9.  

  10. let addString acc item = acc + "\r\t" + item

  11.  

  12. let createParagraphNormal text =

  13.     "<w:p>

  14.       <w:r>

  15.         <w:t>" + encodedXml text +

  16.         "</w:t>

  17.       </w:r>

  18.     </w:p>"

  19.  

  20. let createParagraphBold text =

  21.     "<w:p>

  22.       <w:r>

  23.         <w:rPr>

  24.          <w:b />

  25.         </w:rPr>

  26.         <w:t>" + encodedXml text +

  27.         "</w:t>

  28.       </w:r>

  29.     </w:p>"

  30.  

  31. let createParagraph = function

  32.     | Normal(text) -> createParagraphNormal text

  33.     | Bold (text) -> createParagraphBold text

  34.  

  35. let createParagraphs = function

  36.     | Paragraphs(list) -> list|> List.map createParagraph |> List.reduce addString

  37.  

  38. //test

  39. let p = createParagraphs doc

  40.  

  41. let createDocument doc =

  42.     let startDocument = @"<?xml version=""1.0"" encoding=""utf-8""?>

  43.                     <w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">

  44.                       <w:body>"

  45.     let endDocument = "  </w:body>

  46.                         </w:document>"

  47.     let content = createParagraphs doc

  48.     startDocument + content + endDocument





 

Then we have to create a package, add the document and save it to disk:






  1. //add the document to package and save

  2. let createFile doc (fileName:string) =

  3.     using (Package.Open(fileName, FileMode.Create, FileAccess.ReadWrite))(fun package ->

  4.         let uri = new Uri("/word/document.xml", UriKind.Relative )

  5.         let partDocumentXML =

  6.             package.CreatePart(

  7.                                 uri,

  8.                                 "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" )

  9.     

  10.         using(new StreamWriter(partDocumentXML.GetStream(FileMode.Create, FileAccess.Write)))(fun stream ->

  11.             doc |> createDocument |> stream.Write

  12.         )

  13.  

  14.         package.CreateRelationship(

  15.                                     uri,

  16.                                     TargetMode.Internal,

  17.                                     "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",

  18.                                     "rId1") |> ignore

  19.     )

  20.  

  21. //test

  22. let fileName = @"D:\Tmp\test.docx"

  23.  

  24. createFile doc fileName;;




 
This is the result:

 

I have created a map from twitter to my document model






  1. type UserStatus =

  2.     { UserName : string;

  3.       ProfileImage : string;

  4.       Status : string;

  5.       StatusDate : DateTime }

  6.  

  7. let mapUserStatus (us:UserStatus) =

  8.     let date = "Date: " + us.StatusDate.ToShortDateString()  + " time: " + us.StatusDate.ToLongTimeString()

  9.     [Normal(date); Bold(us.UserName); Normal(us.Status); Normal("")]

  10.  

  11. let addUserStatus acc item = item @ acc

  12.  

  13. let result = xml|> parseTweetXml |> List.map mapUserStatus|> List.reduce addUserStatus





 


This results in the following document:









Remark: I had to Google “Weiner” to understand the jokes.

No comments:

Total Pageviews