In my last post I referenced an excellent blog post on modonovan’s blog which talks you through how to use Word Content controls to map to custom XML. This is a really powerful concept – particularly for generating documents on the server – as there’s no need for the Word client to generate complex Word documents with the combination of the new Open XML file format and Word content controls. The crux of this is the custom XML store in the new file format (I can push my own XML data into the file) and the ability of content controls to late-bind to this data. This allows me to generate the data I want displayed in the document and let the content controls take care of the presentation by simply mapping them using XPath.
In that post, it takes you through how to manually create a Word document, add the custom XML part and create the content control mappings. Well I took a slightly different approach for the session I did at the Office DevCon and built myself a very quick and dirty VB app to automate Word and make things much easier by doing it through the object model. Here’s what the UI looks like (from a usability perspective it’s horrible – I did say it was quick and dirty).
And the code that drives it (with some comments interspersed). I’m not sure how helpful it is to provide a listing (takes me back to typing in listing from C&VG when I was a lad) but I haven’t got a suitable place to drop this code and given its flaky nature (eg there is no exception handling – I ripped out what little there was to make the listing shorter!) I’m unlikely to get it posted on MSDN .
However, it does the job if you’re patient with it – just do things in the right order and it’ll behave itself. The app will do a few useful things for you:
In this way you can start with a Word document with some content controls and very quicky generated the custom XML part, add it to the Word document and map the content controls. You’ll may have to stop and start the app a few times to do this (eg to attach the custom XML part) but it’s pretty straightforward. As the app instantiates Word, it also closes it when you click “Quit”. If you close word yourself the app will throw an exception when you quit.
It’s meant to be quick and dirty as it’s only a means to an end – simply to get the data into the document and set up the XML mappings. Once that’s done the app’s work is done and the Word document is a standalone entity (with no code behind – it’s just a document that has some smarts about mapping the content controls). Next thing to do is to write some .Net Fx 3.0 code to update the custom xml part and see how easy it is to generate truckloads of these things by just pushing in different XML data…
Imports Word = Microsoft.Office.Interop.Word Imports System.Windows.Forms Imports Office = Microsoft.Office.Core Public Class Form1 Dim wordFile As String Dim xmlFile As String Dim WordApp As Microsoft.Office.Interop.Word.Application Dim WordDoc As Microsoft.Office.Interop.Word.Document ' Handler for Word and XML file dialog buttons Private Sub btnPickFile_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnPickWordFile.Click, _ btnPickXmlFile.Click Dim f As New OpenFileDialog() Select Case CType(sender, Button).Name Case "btnPickWordFile" f.DefaultExt = "docx" f.Filter = "Word Document|*.docx" If f.ShowDialog() = System.Windows.Forms.DialogResult.OK Then txtWordFileName.Text = f.FileName wordFile = f.FileName End If Case "btnPickXmlFile" f.DefaultExt = "xml" f.Filter = "XML Document|*.xml" If f.ShowDialog() = System.Windows.Forms.DialogResult.OK Then txtXmlFileName.Text = f.FileName xmlFile = f.FileName End If End Select End Sub ' Handler for Open button ' Starts Word and opens the specified Word file ' If the Attach XML Part checkbox is checked, it adds a CustomXMLPart ' to the document and loads the specified XML document into it Private Sub btnAttach_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnAttach.Click WordApp = New Word.Application() WordApp.Visible = True WordDoc = WordApp.Documents.Open(wordFile, , , , , , , , , , , , , , , , ) If chkAttachXmlPart.Checked = True Then Dim oCustomXMLPart As Office.CustomXMLPart oCustomXMLPart = WordDoc.CustomXMLParts.Add oCustomXMLPart.Load(xmlFile) End If ListContentControls() End Sub ' Displays the content controls in the document in ListBox1 Private Sub ListContentControls() ListBox1.ValueMember = "Title" ListBox1.Items.Clear() For Each wcc As Word.ContentControl In WordDoc.ContentControls ListBox1.Items.Add(wcc) Next If ListBox1.Items.Count > 0 Then ListBox1.SelectedIndex = 0 End If End Sub ' Handler for Set XPath button ' Sets the XMLMapping (an XPath expression) for the selected content control ' which binds that control to a particular piece of data in the XML store Private Sub btnSetXpath_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnXpathSet.Click Dim wcc As Word.ContentControl = _ CType(ListBox1.SelectedItem, Word.ContentControl) If txtXPath.TextLength > 0 And _ wcc.Type <> Word.WdContentControlType.wdContentControlRichText And _ wcc.Type <> Word.WdContentControlType.wdContentControlPicture Then wcc.XMLMapping.SetMapping(txtXPath.Text) End If End Sub ' Handler for Create XML button ' Given a Word document with some content controls in it, generate a ' template XML document containing an element for each control and copy ' this to the clipboard Private Sub btnCreateXML_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnCreateXML.Click Dim textWriter As New System.IO.StringWriter Dim settings As New Xml.XmlWriterSettings settings.Indent = True Dim xmlWriter As Xml.XmlWriter = _ Xml.XmlWriter.Create(textWriter, settings) xmlWriter.WriteStartDocument() xmlWriter.WriteStartElement("RootNode") For Each wcc As Word.ContentControl In WordDoc.ContentControls If wcc.Type <> Word.WdContentControlType.wdContentControlRichText And _ wcc.Type <> Word.WdContentControlType.wdContentControlPicture Then xmlWriter.WriteElementString(wcc.Title, "Sample") End If Next xmlWriter.WriteEndDocument() xmlWriter.Flush() xmlWriter.Close() textWriter.Flush() Clipboard.SetText(textWriter.ToString()) MessageBox.Show("XML doc added to clipboard") textWriter.Close() End Sub ' Update the XPath expression as the selection changes Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ListBox1.SelectedIndexChanged Dim xmlMap As Word.XMLMapping xmlMap = CType(ListBox1.SelectedItem, Word.ContentControl).XMLMapping If xmlMap IsNot Nothing Then txtXPath.Text = xmlMap.XPath End If End Sub Private Sub btnQuit_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnQuit.Click WordApp.Quit() WordApp = Nothing GC.Collect() GC.WaitForPendingFinalizers() GC.Collect() GC.WaitForPendingFinalizers() Me.Close() End Sub End Class
I used c# code format to format this code as it was driving me nuts doing it by hand - a very handy tool!
PingBack from http://blog.cronberg.dk/PermaLink,guid,edc644fe-b12f-4d0a-9c88-0c3cb88a48b1.aspx
PingBack from http://blog.cronberg.dk/LinksTilOffice2007Udvikling.aspx
In my last couple of posts ( here and here ), I've been talking about the goodness that is Word content