theThought's thoughts

Kevin A Gray - Creative Strategy Guy

"one for all" is definitely possible - the Browsers

A couple of weeks ago I wrote an article questioning whether it would be possible to create a single template that handles the vast majority of browsers and allows Data Collection to deliver the best experience each browser can deliver (read Is “one for all” even possible).

I am pleased to announce, after a number of weeks of hard work that the answer is “YES”.

So what has happened over the last few weeks that allowed me to come to this answer?

The steps so far
I have not yet reached the end of my journey, however, I thought it might be a good idea to allow you to catch up.  I have started/completed a number of tasks including:

  • A review of different browsers on the market (or previously on the market) identifying the capabilities they provide
  • Locate and install a number of browsers and emulators into a Test environment
  • Build a small Data collection survey to allow testing
  • Start building a solution

Browsers

I started with the currently installable browsers (Internet Explorer, Firefox, Opera, Safari, Chrome)  Then I tried older versions of IE.  Lastly I obtained some emulators (Blackberry, Sony Ericsson, Nokia), finally I installed a copy of aDesigner for testing JAWS screen reader compatibility.

aDesigner an aSide
If you have not encountered aDesigner before and you are seriously interested in web accessibility you need to look at it now (aDesigner is available here).  aDesigner was built by IBM but it has been donated to the Accessibility Tools Framework (ACTF).  Not only does aDesigner provide clear information about the general level of accessibility of a website (based on its markup) it will also render the website from the perspective of “low vision“ showing how those with poor eyesight will see the site.  Furthermore, if that was not enough, in addition to testing websites it can also test ODF (Open Document Format) files, Flash content and general GUI accessibility of any application.

Image001

3 Classifications of Browsers
Having reviewed a multitude of browser capabilities I ended up with three simple classficiations.  These are based on the capabilities of the browser and will affect the way in which the template will be designed.  The basis of the categorisation is the fact that there are, currently, primarily, two platforms that have to be handled:

  • Desktop Browsers (PC, Mac and others)
  • Mobile Browsers (smartphone, basic phone, portable devices)

Against these two basic platforms there are a number of capabilities that may or may not be used:

  • HTML
  • CSS
  • HTML 5
  • CSS 3
  • JavaScript

All of the browsers support HTML although some only have limited support of CSS.  Only a few have support for HTML 5 and CSS 3 (as I wrote this Microsoft announced that this list will expand to include IE 9 – review by the Register.com).  Lastly there is JavaScript.  This is more complex as browsers that naturally support JavaScript can be “crippled” by corporations that disable its use.  The following graphic shows how these different elements interplay with each other.

Image002
 

From this I defined three classifications of Browser as follows:

Basic Browsers           OpenWare browser, Basic (old) Mobile browsers, very early versions of IE and Netscape (Full HTML coverage, limited CSS coverage, little or no JavaScript coverage)

Simple Browsers         IE, Netscape, RIM, Nokia browser (Full HTML/CSS coverage, no HTML 5/CSS 3 coverage, some JavaScript Coverage)

Rich Browsers            Chrome, Firefox, Safari, Sony Ericsson (Full HTML/CSS and HTML 5/CSS 3 coverage, Full JavaScript Coverage)

Filed under  //   ACTF   Blackberry   Browsers   CSS   CSS 3   Chrome   Emulators   Firefox   Flash   Form Field   HTML   HTML 5   IBM   IBM SPSS Data Collection   JAWS   JavaScript   Netscape   Safari   Simulators   aDesigner  

1903 days, but no longer counting

Today - 29th January 2010 is my last day at SPSS, and IBM Company.  

The last five years have been dramatic and very interesting.  I have been given an opportunity to take IBM SPSS Data Collection (formerly Dimensions) from a script orientated web-survey platform for high-end market researchers to a truly versatile interviewing solution where the respondent can choose the mode of interviewing that fits their lifestyle.  

But enough of the marketing spin.  I hope that I have helped those companies out there who are using Data Collection by delivering a number of high quality releases and transitioning the Development team from a two small groups based in the UK and Denmark to a large team based in Xi-An supported by a smaller team based in Rochester.

It has not been all smooth running, the team has had its issues and so has the product, but I leave with the expectation that Data Collection is very much on the up.  The 5.6 release is solid and the next release (currently in the latter stages of development) will deliver a big step up in various areas.

I have been well supported, especially by my boss (Jason Verlen) and my team (Nick Read and Hetal Thaker).  They remain to ensure that the effort I have put in to delivering consistency and transparency to the Strategic direction of the product set is not wasted.

There are many unsung heroes in the team including Rob Bristow ( a stalwart in support), Lance Nichols (a naturally gifted engineer), Allison Carleton (who terrified me in my interview but was great to work with), Jane Moore (without whom customer engagements would not have been so exciting)

Finally to Colin Linsky who was one of two people who saw something during my initial interview that made them want to offer me a job and then has been working with me ever since, challenging my abilities and supporting my efforts.  Thank you very much to all of you and the many others who I have not mentioned individually but were equally an important part of the team.

In leaving it is often customary to ponder the good and bad points, I have always been a keen evangelist and I hope that those who have attended my presentations at various events have enjoyed the experience.  I would certainly like to say thanks to all my groupies (I wont mention names) who asked intelligent questions at the right time and clapped loudly at the end.  You always made it worth while.

I joined SPSS from the world of New Media and the one major area of disappointment during my time was the lack of understanding of the need for creativity and community. It is my personal opinion that this is a flaw with SPSS that comes from its history and is very difficult to change (even if they wanted to) It is probably this that is the main cause for my departure.  I missed these things too much and want to get them back.

So where am I going? well to find that out you will have to wait till Monday ...

Filed under  //   IBM   IBM SPSS Data Collection   SPSS an IBM Company  

Success IBM SPSS Data Collection integrates with WordML (OpenXML)

SUCCESS

It’s taken a few weeks, in which the company has changed name, the product has changed name (again) and I have been busy getting ready for everything.  But 10:00 am (GMT) today I managed to run my first survey where responses from the survey were posted into a Custom Part within a Word Document so that the Word document is automatically updated.  If nothing else this proves that IBM SPSS Data Collection can integrate with OpenXML.

Although there are a few other things I would like to do, generate a PDF from the document, do all the work in memory so that the document is not actually written to file, send the file as an attachment to an email.  But this is a great start.

So Monday will see me move to my third project (the above being my second, after a very simple manipulation of WordML in my first).  I will be moving from Word to PowerPoint and I will be moving from Collection to analysis.  I would like to be able to show that output from an MRS (Reporting Script) can push data into an existing Powerpoint presentation without losing any formatting.

One work day off then and then back to the grindstone (what I really mean is one day with my Flash project before moving back to OpenXML – but I can pretend it’s a day off.)

I hope you have enjoyed the posts so far and will continue to read about my endeavours in the future.

Filed under  //   Data Collection   IBM   Microsoft Word   OpenXML   SPSS   WordML  

IBM SPSS Data Collection Consumes DLL to push data into OpenXML

 

So the class required to push data into an OpenXML Word document has now been created (the class), there are just three steps left:

·         Create the DLL wrapper and publish

·         Create a test Chassis to ensure that the DLL works and resolve issues

·         Integrate the DLL into the Data Collection routing and test the final integration

Details of each of these steps is below:

Create the DLL wrapper and publish

Data Collection is able to integrate with processes that are exposed as COM components.  At present the DLL that has been created is not exposed in this way consequently some changes have to be made. This is done by changing the definition of the class and is described by the following article (com objects in vb.net).  Many of these steps have already taken place.  As this particular class does not use or consume events I did not create an Events Id.  Also I used Visual Basic Express 2008 to create my DLL and this does not have the GUID creating tool referenced in the document.  Consequently I used a website to do this (oh the beauty of the internet),  this site is fantastic in its simplicity (create a GUID).

Effectively I did two things:

I replaced the Class definition

Public Class Complaint

with

<System.Runtime.InteropServices.ProgId("cao2.Complaint"), ComClass(Complaint.ClassId, Complaint.InterfaceId)> Public Class Complaint

   Public Const ClassId As String = "CB3F9E3E-D1EB-4852-9C34-7C0A6DC4F7F8"
   Public Const InterfaceId As String = "9C10A919-8E4B-419E-9AAE-3868A1466EC4"

Following this two Constants are generated for the ClassID and InterfaceID GUIDs.  They are kept as constants to make sure that they are the same between compilations of the DLL

Lastly the Properties of the Project are changed so that the Assembly knows it needs to make the assemblies COM visible.  This setting is shown in the following diagram:

Image001

Create a test Chassis to ensure that the DLL works and resolve issues

Once the DLL has been constructed it needs to be tested.  A test chassis is needed to be able to define the DLL and pass the UpdateCustomPart method with the necessary details.  I used a project I had created earlier (first project) that I built as a chassis as it has a form interface and already has the right references. I added a RichTextBox so that the output from the survey (survey output) could be copied into the Chassis and passed to the DLL.  The form now like the one shown below:

Image002

Once the form was finished the references within the project were changed so that the chassis is aware of the newly created DLL.  The reference can be seen on the second line of the following diagram.  It was created by adding a reference and pointing to the DLL that was created when the DLL was compiled.

Image003

Next the start button was re-coded so that it creates a new instance of the RailComplaint class and then calls the UpdateCustomPart passing the relevant information into the method.  The information it passes is the name and location of the Word Document (hard coded for this test), the name of the CustomPart (again hard coded), and the content of the rich text box that should contain the XML string from the survey.

Private Sub pbnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles pbnStart.Click

‘Update Custom Part Test

Dim RailComplaint As New RailComplaint.Complaint
Dim Result As Boolean

Result = RailComplaint.UpdateCustomPart("C:\projects\OpenXML\Samples\RailComplaint\Customer Complaint Test.docx", "item1", rtbXML.Text)

End Sub

Once this is complete IBM SPSS Data Collection – Author Professional is run  and the survey is loaded.  Auto-Answer is used to populate the responses and generate the XML.  The last line of code sends the XML to the output window: Debug.log(xmlPart.xml).  Once the survey is complete the contents of the output window can be copied into the RichTextbox in the test Chassis.  Finally, the start button can be pressed and the code executed.  The script provided in this and other posts has already gone through this process so should work without issue.

Integrate the DLL into the Data Collection routing and test the final integration

The final stage of the process is to call the DLL from the survey.  The code used to do this is very similar to that used in the test harness.  DCScript variables are not typed so the declarations are simpler.  The last line just makes sure that the RailComplaint object is completely removed from memory.

Dim Result
Dim RailComplaint          

                set RailComplaint = CreateObject("cao2.Complaint")
                Result = RailComplaint.UpdateCustomPart("C:\projects\OpenXML\Samples\RailComplaint\Customer Complaint Test.docx", "item1", xmlPart.xml)
                set RailComplaint = null

Once this has been done it is possible to run the survey, complete the two pages of questions and then Open the Customer Complaint Text.docx file to see the result.

Filed under  //   CreateObject   DCScript   DLL   IBM   OpenXML   PASW DATA Collection   SPSS   WordML  

Data Collection to OpenXML a simple class for populating custom parts (the code)

The previous post provided a technical specification for the DLL required to link IBM SPSS Data Collection to the Custom Part in a Word document so that the responses in the survey can be pushed into the Word document.  This post will show the code used to implement that specification.  The code has been broken down into a simplified form to help me learn and to make it easier to explain.  There will be shortcuts that you may want to adopt, you may want less functions preferring to write the code in a single method, however I prefer the small is beautiful approach.

Defining the class:
As OpenXML constructs are going to be manipulated it is necessary to define the right references within the project and to import them into the class itself.  As streams are going to be used to read and write xml files into the Word document System.IO has to be imported.  There is no constructor required as the key actions will be on the main method (UpdateCustomPart). Consequently the class definition is as follows:

Imports DocumentFormat.OpenXml.Packaging

Imports System.IO

Public Class Complaint

End Class

UpdateCustomPart (Public Method)
The first and main method is the UpdateCustomPart, this will receive three parameters and will, for the purpose of this example, output a Boolean indicating success or fail.   The process this method follows is:

Ø  Parse XML string into a new XML Document

Ø  Open the Word Document

Ø  Locate the Custom Part

Ø  Update the Custom Part

Within this process the XML String, the name and location of the Word document and the name of the Custom Part are passed as parameters so that the same class could be used to update any custom part within any Word document.  The code for this method is as follows:

Public Function UpdateCustomPart(ByVal theFileName As String, ByVal theCustomPart As String, ByVal theXML As String) As Boolean

Dim WordDoc As WordprocessingDocument
Dim CustomPart As DocumentFormat.OpenXml.Packaging.OpenXmlPart
Dim xmlUpdate As Xml.XmlDocument

    xmlUpdate = New Xml.XmlDocument

    Try
        xmlUpdate.LoadXml(theXML)
    Catch ex As Exception
        Return False
    End Try

    WordDoc = OpenWordDoc(theFileName)

    If Worddoc Is Not Nothing Then CustomPart = FindCustomPart(WordDoc, theCustomPart)

    If CustomPart Is Not Nothing Then

Return changeCustomPart(CustomPart, xmlUpdate)

   End If

Return True

End Function

In the above function the try … catch is used to capture any errors generated from loading the XML (caused by badly formed xml).  Each of the subsequent functions are detailed below, if any fail the subsequent actions are not performed.

OpenWordDoc (Private Function)
The purpose of this function is to open the Word document identified by the path/filename string and to return the opened document to the calling routine.  Rather than just attempt to open the file (only to have it fail because the file does not exist) the function first uses FindFile to locate the document.  Only if this returns details of the file will the system attempt to open it. 

Private Function OpenWordDoc(ByVal theFileName As String) As WordprocessingDocument

Dim WordDoc As WordprocessingDocument
Dim FindFile As System.IO.FileInfo

    FindFile = New System.IO.FileInfo(theFileName)

    If Not FindFile Is Nothing Then
        WordDoc = WordprocessingDocument.Open(theFileName, True)
        Return WordDoc
    Else
        Return Nothing
    End If

End Function

FindCustomPart (Private Function)
This function was actually quite difficult to work out, it maybe that it is not actually the best way of doing it.  The word document is a package that contains a significant number of XML sub-documents.  It is necessary to find the right XML document to update.  The word document was created from within Word and the Word 2007 Content Control Toolkit.  As a consequence I did not name the Custom Part it was named for me.  Its name is Item1.xml.  It is contained in the customXml folder.  This document can be located using the relationships definition for the Main document part.  If this relationship definition is examined it can be seen that there is a relationship described for the custom part.

<?xml version="1.0" encoding="utf-8"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
                <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml" Id="rId3" />
                <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml" Id="rId7" />
                <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml" Id="rId2" />
                <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/glossaryDocument" Target="glossary/document.xml" Id="rId6" />
                <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml" Id="rId5" />
                <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml" Id="rId4" />
                <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml" Target="../customXml/item1.xml" Id="R456ea5aec8a94caf" />
</Relationships>

The target attribute is the URI of the part.  Based on the fact that the name of the part is provided by the consumer of the DLL this URI can be constructed through some simple text manipulation (“customXml/” + thename + “.xml”).  There may be several custom parts within a document so the Find function iterates through all the custom parts matching the URI of the custom part with a calculated URI.  When it finds a match it returns the matching part.  If it does not find a matching URI the function returns nothing.

Private Function FindCustomPart(ByVal theDoc As WordprocessingDocument, ByVal theName As String) As DocumentFormat.OpenXml.Packaging.CustomXmlPart

Dim CustomPart As DocumentFormat.OpenXml.Packaging.CustomXmlPart
Dim MainParts As IEnumerable(Of CustomXmlPart)
Dim ID As String
Dim matchURI As Uri
Dim CalculatedLocation As String = "/customXml/" & theName & ".xml"

    MainParts = theDoc.MainDocumentPart.CustomXmlParts

    matchURI = New Uri(CalculatedLocation, UriKind.Relative)

    For Each CustomPart In MainParts
        ID = theDoc.MainDocumentPart.GetIdOfPart(CustomPart)

        If CustomPart.Uri = matchURI Then
            Return CustomPart
        End If

    Next

    Return Nothing

End Function

ChangeCustomPart (Private Function)
This function uses a stream to Create new content for the custom part using the XML document created from the XML string passed to the DLL.  It effectively ties all the pieces together.  The writing of the stream updates the file (effectively saves it) this means that there is no need to save the document itself.

Private Function changeCustomPart(ByVal thePart As DocumentFormat.OpenXml.Packaging.OpenXmlPart, ByVal theXML As Xml.XmlDocument) As Boolean

Dim XMLStream As Stream

    XMLStream = thePart.GetStream(FileMode.Create, FileAccess.ReadWrite)

    Using (XMLStream)
        theXML.Save(XMLStream)
    End Using

End Function

Filed under  //   IBM   IBM SPSS Data Collection   OpenXML   SPSS   VB.net  

Data Collection to OpenXML a simple class for populating custom parts (Technical Design)

So far in this project a survey (the survey) has been created that accepts details of a train complaint.  A Word document (the word doc) has been created that provides a printed copy of the complaint details and a custom part (the custom part) has been defined and added to the Word document to facilitate the transfer of information from IBM SPSS Data Collection to Microsoft Word.  In the last post (creating XML in Data Collection) logic was added to the end of the survey that creates an XML document containing all the responses to the survey in the same structure as the custom part in the Word document.  In this post VB will be used to create a simple class that will accept the XML document and re-build the custom part.  It will also include methods to convert the resulting document into a PDF ready for emailing to the respondent.

Technical Design

The purpose of this DLL is to handle the interaction  between Data Collection and an OpenXML document.  The class will receive the XML to be inserted into the document and the details of the custom part to be updated.  IT will then replace the content of the existing custom part with the new XML content.  It will not delete the custom part itself as this would result in the loss of the existing unique ID for the document. This would in turn cause all the document relationships and form field bindings to break.

The second part of the process is to create a PDF version of the document.  Word can now do this without the use of third party components.  The returned PDF can then be attached to an e-Mail for distribution to the survey respondent (complainant)

Update Part method
Inputs:

·         XML (containing the survey responses)

·         Document name (the name of the Word document to change)

·         Custom Part name (name of the custom part to be changed)

Outputs

·         Success/Fail Signal (indicating whether update succeeded)

Create PDF Method

Inputs: None

Outputs:

·         PDF version of document

Internal Functions

As well as methods exposed to other applications (in this case IBM SPSS Data Collection) this dll will require some internal functions.

OpenWordDoc (Opens the document defined by the Update Part Method)

Locate Custom Part (takes the open document and finds the custom part)

Adjust Custom Part (having found the custom part the XML needs to be fed in)

There is no need to save the document as the OpenXML SDK will automatically save when the custom part is changed.

Filed under  //   Custom Part   Data Collection   IBM   IBM SPSS Data Collection   OpenXML   SPSS   VB.net   XML  

Taking a step back: Data Collection creates XML to populate OpenXML Custom Part

 

Over the last week I have been attempting to complete the project so that I can ensure I document the last few steps correctly.  In the last post I created the Data Collection routing for generating the XML that needs to be pushed into the Custom Part.  Since then I managed to create a automaton for pushing the XML into the custom part, however, when testing it in a test chassis I found that Word was struggling to understand attributes.  The XML I created manually was fine but the XML generated through the script did not work.  Having determined this but not determined why I have decided to take a step back and change the way that Data Collection creates the XML so that Word will work.

What do I mean: “Word does not work”?  Well when I manually created the CustomPart and bound it to the Form Fields you could see the results on the page (binding form fields) as shown in the image below.  Once I had automated the creation of the XML and the pushing of that XML into Word the content of the custom part was not showing in the document (the form was blank). 

Image001

If I then went into Design mode to view the Form Field details and then switched back out of Design Mode I would get an error.  It took me quite some time to realise that this error was simply saying that the Form Fields are empty.  This seems a bad error (one that Microsoft needs to change).

Image002

Furthermore as the PASW name for Data Collection has been dropped by IBM, I decided to change my namespace from PASW to CaO2.  CaO2 is an old term from a previous life, it has nothing to do with SPSS or IBM but I like using it (for example I use it as a domain name for my e-Mail: kgray@cao2.net)

To explain the change, let’s examine the name node of the xml.  In the previous post the following XML would have been generated by Data Collection:

<cao2:name cao2:title=”Mr” cao2:fname=”Kevin” cao2:lname=”Gray” />

Word did not seem to like the namespace qualified attributes.  Dropping the namespace for each attribute did work. So I could change the XML to:

<cao2:name title=”Mr” fname=”Kevin” lname=”Gray” />

However I am not happy doing this I like the namespace to be qualified properly.  Consequently I have decided to only use textnodes and not use attributes.  This will get me through this exercise but is not a perfect solution.  As a consequence I will have to return to this in a later blog to ensure I can use attributes in the long run.  The new XML will, consequently look like this:

<cao2:name>
                <cao2:title>Mr</cao2:title>
                <cao2:fname>Kevin</cao2:fname>
                <cao2:lname>Gray</cao2:lname>

</cao2:name>

This format is much more verbose but it does work when used in Word.  In order to have this format of output the routing in Data Collection needs to change.  The creation of an attribute was, previously,  executed through the following code (which creates the name node and then adds a title attribute):

set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:name", "http://www.kevinagray.co.uk/railcomplaint")
xmlPersonal.appendChild(xmlNode)     

if Title.Response.Value <> null then
                xmlNode.setAttribute("cao2:title", format(Title.Response,"b"))
else
                xmlNode.setAttribute("cao2:title", "")
end if

The new code will still create the name node but then will create another node for title.  The title node is always created whether or not there is a title, however the textnode within the new node is only created if there is response on the form, otherwise the title node will be null.

set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:name", "http://www.kevinagray.co.uk/railcomplaint")
xmlPersonal.appendChild(xmlNode)

set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:title", "http://www.kevinagray.co.uk/railcomplaint")
xmlNode.appendChild(xmlParent)

if Title.Response.Value <> null then
                set xmlText =  xmlPart.createTextNode(format(Title.Response,"b"))
                xmlParent.appendChild(xmlText)
end if

The full code in the routing is now:

Routing(Web)

Dim xmlPart, xmlNode, xmlRoot, xmlPersonal, xmlIncident, xmlParent, xmlText
Const Node_Element = 1

                IOM.LayoutTemplate = "railcomplaint"
                IOM.MustAnswer = false            

                Personal.Ask()

                Incident.Ask()

                set xmlPart = CreateObject("Microsoft.XMLDOM")
                set xmlRoot = xmlPart.createNode(Node_Element, "cao2:root", "http://www.kevinagray.co.uk/railcomplaint")
                xmlPart.appendChild(xmlRoot)

                set xmlPersonal = xmlPart.CreateNode(Node_Element, "cao2:personal", "http://www.kevinagray.co.uk/railcomplaint")
                xmlRoot.appendChild(xmlPersonal)

                set xmlIncident = xmlPart.CreateNode(Node_Element, "cao2:incident", "http://www.kevinagray.co.uk/railcomplaint")
                xmlRoot.appendChild(xmlIncident)

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:when", "http://www.kevinagray.co.uk/railcomplaint")
                xmlIncident.appendChild(xmlParent)

                if IncidentDate.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(IncidentDate.Response.Value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:destination", "http://www.kevinagray.co.uk/railcomplaint")
                xmlIncident.appendChild(xmlParent)

                if IncidentDestination.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(IncidentDestination.Response.Value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:name", "http://www.kevinagray.co.uk/railcomplaint")
                xmlPersonal.appendChild(xmlNode)     

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:title", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if Title.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(format(Title.Response,"b"))
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:fname", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if fname.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(fname.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:lname", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if lname.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(lname.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:address", "http://www.kevinagray.co.uk/railcomplaint")
                xmlPersonal.appendChild(xmlNode)

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:line1", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if address1.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(address1.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:line2", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if address2.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(address2.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:town", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if Town.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(town.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:postcode", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if PostCode.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(postcode.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:contact", "http://www.kevinagray.co.uk/railcomplaint")
                xmlPersonal.appendChild(xmlNode)

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:work", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if Phone.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(phone.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:home", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if Phone2.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(Phone2.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:mobile", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if mobile.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(mobile.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:email", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if email.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(email.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:departure", "http://www.kevinagray.co.uk/railcomplaint")
                xmlIncident.appendChild(xmlNode)

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:from", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if IncidentDepart.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(IncidentDepart.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:expected", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if IncidentExpected.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(IncidentExpected.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:actual", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if IncidentActual.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(IncidentActual.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:impact", "http://www.kevinagray.co.uk/railcomplaint")
                xmlIncident.appendChild(xmlNode)

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:severity", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if IncidentSeverity.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(IncidentSeverity.Response. value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:outcome", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if ExpectedOutcome.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(ExpectedOutcome.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlParent = xmlPart.CreateNode(Node_Element, "cao2:cost", "http://www.kevinagray.co.uk/railcomplaint")
                xmlNode.appendChild(xmlParent)

                if CostIncurred.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(CostIncurred.Response.value)
                                xmlParent.appendChild(xmlText)
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:description", "http://www.kevinagray.co.uk/railcomplaint")
                xmlIncident.appendChild(xmlNode)

                if IncidentDescription.Response.Value <> null then
                                set xmlText =  xmlPart.createTextNode(IncidentDescription.Response.Value)
                                xmlParent.appendChild(xmlText)
                end if

                debug.Log(xmlPart.xml)

End Routing

Filed under  //   CustomPart   IBM   IBM SPSS Data Collection   OpenXML   Routing   SPSS  

Updating a Custom Part from PASW Data Collection when using OpenXML for Word (WordML)

Interlude
During the writing of this post the formal announcement regarding the acquisition of SPSS by IBM was made (IBM acquires SPSS Inc).  I now, officially work for SPSS (UK Limited), an IBM Company.  The product I run is no long PASW Data Collection but IBM SPSS Data Collection.  As a result there will be no more references to PASW.
End if Interlude

In previous posts I have explored the components required to publish a Word Document that contains a form that can be populated from IBM SPSS Data Collection.  The Word document was created using Form Fields within a table.  The Word 2007 Content Control toolkit (available here) was used to create a custom part, a slice of custom XML defined not by Word but by the developer.  The same tool was then used to link the custom parts to the form fields in the table (Binding custom parts to form fields).  All this work has resulted in a Word document that is using custom XML to define the content of the table.

The purpose of this post is to go back to IBM SPSS Data Collection to build the XML ready to be sent to Word for posting into the Word document.  There are two ways in which this can be done:

Option 1
As the custom part already exists and contains the correct structure for the XML updating the document should be just a case of updating the attributes contained within the XML.  To ensure that the custom part contains the correct information all existing attributes would have to be updated.

Option 2
Create a completely new custom part that replaces the existing custom part but keeps the same ID. 

It may be that these two techniques will blend together.  The current custom part will be read through a stream, updated and then written back.  There are only two key questions to be answered:

  • Whether it necessary to take any notice of the content that has been read into the stream?
  • Whether PASW Data Collection should assemble the new XML as a single data construct and then pass it to the dll that will post the information into the custom Part?

In the latter case the alternative is for IBM SPSS Data Collection to converse with the dll sending attribute/value pairs each time it wants to update an element of the document.  As the latter is likely to result in multiple reads of the Word document it is not considered suitable for this particular scenario.  If the document was being collaborated on such that two or more users may want to see the changes taking place in real-time it is likely that this approach would be used.

Consequently the plan at this time is to have the routing of the survey create an XML structure from the responses provided and to send that XML data structure to Word via a DLL that handles the updating of the custom part.

Creating the XML to send to the custom part
IBM SPSS Data Collection writes scripts in a VBscript like language.  It is very extendible being able to access the capabilities of any external com components available to it.  The syntax used, consequently, to build a new XML file is very recognisable to those who have written VBscript or DCScript (Data Collection Script) before:

set xmlPart = CreateObject("Microsoft.XMLDOM")

Data Collection Script (DCScript) does not used strongly typed variables but the variables do have to be declared.  Consequently a Dim statement for xmlPart is required before this script.  Once this is done it is possible to create the relevant nodes.  The first is the root node that appears at the top of the XML.  This would usually be done using createElement method, however, this method does not support namespaces so the more complex createNode is required:

set xmlRoot = xmlPart.createNode(Node_Element, "cao2:root", "http://www.kevinagray.co.uk/railcomplaint")
xmlPart.appendChild(xmlRoot)

The createNode method has a constant Node_Element that has a value of 1.  This constant is declared at the top of the script.  The createNode is followed by an appendChild method to add this node to the XML document.  This process can be repeated for the personal and incident nodes.  In the case of the incident node there is two attributes associated to this node.  The code used to generated these two attributes is the same as the code used to generate almost all the attributes in the XML document.  In each case an IF statement is used to check whether there was a response to the question.  If a response is present the value is applied to the Incident Node using the setAttribute method, otherwise the setAttribute method is used to create a blank value.  This ensures that the attribute is present so that the Word document can be mapped correctly and the structure of the XML is consistent.

if IncidentDate.Response.Value <> null then
                xmlIncident.setAttribute("cao2:when", IncidentDate.Response.Value)
else
                xmlIncident.setAttribute("cao2:when", "")
end if

if IncidentDestination.Response.Value <> null then
                xmlIncident.setAttribute("cao2:destination", IncidentDestination.Response.Value)
else
                xmlIncident.setAttribute("cao2:destination", "")
end if

The exception to this methodology is the Title and Severity questions.  Both of these questions are categorical (single punch) and so the response.value is an array (e.g. {Mr}).  Even though, being single punch these responses only ever have one element in the array it is necessary to turn the response into a text string before applying it to the XML.  Data Collection provides the format function to convert (amongst other things) categorical responses into either their categorical name or their categorical label.

Format(questionname.response.value, “a”) provides the name of the category

Format(questionname.response.value,”b”) provides the label of the category

Although as a rule DCScript is not case sensitive.  This function is one of the exceptions.  It is essential that the code (“a” or “b”) is in lower case to deliver the right result.  As a consequence the writing of these responses into the XML looks like this:

if Title.Response.Value <> null then
                xmlNode.setAttribute("cao2:title", format(Title.Response.Value,"b"))
else
                xmlNode.setAttribute("cao2:title", "")
end if

The whole routing is as follows:

Routing(Web)

Dim xmlPart, xmlNode, xmlRoot, xmlPersonal, xmlIncident
Const Node_Element = 1

                IOM.LayoutTempla te = "RailComplaint.htm"

                Personal.Ask()

                Incident.Ask()

                set xmlPart = CreateObject("Microsoft.XMLDOM")
                set xmlRoot = xmlPart.createNode(Node_Element, "cao2:root", "http://www.kevinagray.co.uk/railcomplaint")
                xmlPart.appendChild(xmlRoot)

                set xmlPersonal = xmlPart.CreateNode(Node_Element, "cao2:personal", "http://www.kevinagray.co.uk/railcomplaint")
                xmlRoot.appendChild(xmlPersonal)

                set xmlIncident = xmlPart.CreateNode(Node_Element, "cao2:incident", "http://www.kevinagray.co.uk/railcomplaint")
                xmlRoot.appendChild(xmlIncident)

                if IncidentDate.Response.Value <> null then
                                xmlIncident.setAttribute("cao2:when", IncidentDate.Response.Value)
                else
                                xmlIncident.setAttribute("cao2:when", "")
                end if

                if IncidentDestination.Response.Value <> null then
                                xmlIncident.setAttribute("cao2:destination", IncidentDestination.Response.Value)
               
else
                                xmlIncident.setAttribute("cao2:destination", "")
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:name", "http://www.kevinagray.co.uk/railcomplaint")
                xmlPersonal.appendChild(xmlNode)

                if Title.Response.Value <> null then
                                xmlNode.setAttribute("cao2:title", format(Title.Response.Value,"b"))
                else
                                xmlNode.setAttribute("cao2:title", "")
                end if

                if fname.Response.Value <> null then
                                xmlNode.setAttribute("cao2:fname", fname.response.Value)
                else
                                xmlNode.setAttribute("cao2:fname", "")
                end if

                if lname.Response.Value <> null then
                                xmlNode.setAttribute("cao2:lname", lname.response.Value)
                else
                                xmlNode.setAttribute("cao2:lname", "")
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:address", "http://www.kevinagray.co.uk/railcomplaint")
                xmlPersonal.appendChild(xmlNode)

                if address1.Response.Value <> null then
                                xmlNode.setAttribute("cao2:line1", address1.Response.Value)
                else
                                xmlNode.setAttribute("cao2:line1", "")
                end if

                if address2.Response.Value <> null then
                                xmlNode.setAttribute("cao2:line2", address2.Response.Value)
                else
                                xmlNode.setAttribute("cao2:line2", "")
                end if

                if Town.Response.Value <> null then
                                xmlNode.setAttribute("cao2:town", town.Response.Value)
                else
                                xmlNode.setAttribute("cao2:town", "")
                end if

                if postcode.Response.Value <> null then
                                xmlNode.setAttribute("cao2:postcode", postcode.Response.Value)
                else
                                xmlNode.setAttribute("cao2:postcode", "")
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:contact", "http://www.kevinagray.co.uk/railcomplaint")
                xmlPersonal.appendChild(xmlNode)

                if Phone.Response.Value <> null then
                                xmlNode.setAttribute("cao2:work", Phone.Response.Value)
                else
                                xmlNode.setAttribute("cao2:work", "")
                end if

                if Phone2.Response.Value <> null then
                                xmlNode.setAttribute("cao2:home", Phone2.Response.Value)
                else
                                xmlNode.setAttribute("cao2:home", "")
                end if

                if Mobile.Response.Value <> null then
                                xmlNode.setAttribute("cao2:mobile", Mobile.Response.Value)
               
else
                                xmlNode.setAttribute("cao2:mobile", "")
                end if

                if eMail.Response.Value <> null then
                                xmlNode.setAttribute("cao2:email", eMail.Response.Value)
                else
                                xmlNode.setAttribute("cao2:email", "")
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:departure", "http://www.kevinagray.co.uk/railcomplaint")
                xmlIncident.appendChild(xmlNode)

                if IncidentDepart.Response.Value <> null then
                                xmlNode.setAttribute("cao2:from", IncidentDepart.Response.Value)
                else
                                xmlNode.setAttribute("cao2:from", "")
                end if

                if IncidentExpected.Response.Value <> null then
                                xmlNode.setAttribute("cao2:expected", IncidentExpected.Response.Value)
                else
                                xmlNode.setAttribute("cao2:expected", "")
                end if

                if IncidentActual.Response.Value <> null then
                                xmlNode.setAttribute("cao2:actual", IncidentActual.Response.Value)
                else
                                xmlNode.setAttribute("cao2:actual", "")
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:impact", "http://www.kevinagray.co.uk/railcomplaint")
                xmlIncident.appendChild(xmlNode)

                if IncidentSeverity.Response.Value <> null then
                                xmlNode.setAttribute("cao2:severity", format(IncidentSeverity.Response.Value, "b"))
                else
                                xmlNode.setAttribute("cao2:severity", "")
                end if

                if ExpectedOutcome.Response.Value <> null then
                                xmlNode.setAttribute("cao2:outcome", ExpectedOutcome.Response.Value)
                else
                                xmlNode.setAttribute("cao2:outcome", "")
                end if

                if CostIncurred.Response.Value <> null then
                                xmlNode.setAttribute("cao2:cost", CostIncurred.Response.Value)
                else
                                xmlNode.setAttribute("cao2:cost", "")
                end if

                set xmlNode = xmlPart.CreateNode(Node_Element, "cao2:description", "http://www.kevinagray.co.uk/railcomplaint")
                xmlIncident.appendChild(xmlNode)

                if IncidentSeverity.Response.Value <> null then
                                xmlPart.createTextNode(IncidentDescription.Response.Value)
                else
                                xmlPart.createTextNode("")
                end if

                debug.Log(xmlPart.xml)

End Routing

 

Filed under  //   DCScript   IBM   IBM SPSS Data Collection   OpenXML   PASW Data Collection   SPSS   VBScript   XML  

OpenXML, Custom Parts and Microsoft Word Document Bindings (from the inside out)

 

The project has gone well so far, I have been able to create the basic building blocks required to have PASW Data Collection feed data into a Word document without having to worry about formatting or structure of the document.  I have created and tested the survey metadata (the survey) , I have defined the XML structure (the custom part) that will be used to carry the data and I have loaded that structure into a custom part and bound it to a word document (the binding) containing form fields that expose the data in the document (the Word document).

Before starting the automation process within the survey I need to better understand how the word document has changed as a result of creating the custom part, the fields and the bindings.  To this end I opened the Word document using WinZip to see it contents:

Image001

As can be seen from this image there are three new documents relating to the custom part.  They are located in a /customxml/ folder within the document:

·         Item1.xml
·         ItemProps1.xml
·         Item1.xml.rels

Item 1 is the actual custom part.  It contains exactly the same structure and content as the original XML document that I created manually (this original file can now be deleted as it is no longer required). ItemProps1.xml indicates that Item1.xml is a custom  part by giving it a data store property.  The contents of this file are very simple:

<ds:datastoreItem ds:itemID="{98ec6525-b02f-47c2-9730-255f5d4ac3b3}" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml" />

Lastly the item1.xml.rels (which is located in a _rels subfolder) contains the relationship between these two documents:

<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
    <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps" Target="/customXml/itemProps1.xml" Id="R14220311e2e8474c935615afbceff9ee" />
</Relationships>

In addition the relationships file for the MainPart of the word document contains a relationship to the customxml part:

    <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml" Target="../customXml/item1.xml" Id="R48d0c0ac84aa4cd0" />

The bindings between the XML Part and the document are located in the MainPart document and are embedded within the definition of the table.  The following WordML structure defines the cell that contains the prompt “Date:” in bold:

<w:tc>
<w:tcPr>
            <w:tcW w:w="2094" w:type="dxa" />
      </w:tcPr>
      <w:p w:rsidR="00C96C26" w:rsidRPr="00EE373A" w:rsidRDefault="00C96C26" w:rsidP="00BE254F">
            <w:pPr>
              <w:rPr>
                <w:b />
              </w:rPr>
            </w:pPr>
           <w:r w:rsidRPr="00EE373A">
                  <w:rPr>
                      <w:b />
                  </w:rPr>
                  <w:t>Date:</w:t>
            </w:r>
</w:p>
</w:tc>

This xml snippet defines a table cell (<w:tc>) whose properties define its with (2094 twips).  Inside the cell is a paragraph inside of which there is a run.  Both the paragraph and the run are bold (<w:b />).  The text in the run is Date:.

Following this is a Structured Document Tag (<w:std>).  This is the form field.

<w:sdt>
<w:sdtPr>
            <w:dataBinding w:prefixMappings="xmlns:ns0='http://www.kevinagray.co.uk/railcomplaint'" w:xpath="/ns0:root[1]/ns0:incident[1]/@pasw:when" w:storeItemID="{98ec6525-b02f-47c2-9730-255f5d4ac3b3}" />
            <w:alias w:val="IncidentDate" />
            <w:tag w:val="IncidentDate" />
            <w:id w:val="5078378" />
            <w:placeholder>
              <w:docPart w:val="DefaultPlaceholder_22675703" />
            </w:placeholder>
            <w:showingPlcHdr />
            <w:text />
</w:sdtPr>
      <w:sdtContent>

       <w:tc>
<w:tcPr>
                 <w:tcW w:w="4994" w:type="dxa" />
         </w:tcPr>
         <w:p w:rsidR="00C96C26" w:rsidRPr="005B25BB" w:rsidRDefault="00C96C26" w:rsidP="00C96C26" />
     </w:tc>
</w:sdtContent>
</w:sdt>

The properties of the Structured Document Tag indicate a data binding (w:databinding).  This binding includes the xpath reference to the relevant attribute within the custom part.  How does the document know which custom part it is referring to (if there were multiple custom parts)?  The w:StoreItemID attribute in the Databinding refers to the same attribute in the custom part definition stated earlier in this post.

The strangest part of this structure (to my eyes) is that the table cell containing the Incident Date is contained within the Structured Document Tag rather than the Structured Document Tag being contained within the table cell.  This seems strange as the XML definition is opposite to the way it is visualised within the document.

Generally, this packaging of information is very neat and means that the updating of the data within the document requires only that the custom part Item1.xml be recreated.  As long as the structure of this document remains the same then the bindings will continue to operate.

Data Collection Populates form using custom controls (binding custom parts to form fields)

Having created the Survey, the Form and defined the custom part it is now time to create a working prototype.  This involves creating a custom part within the document and then linking the relevant data from the custom part to each of the forms.  Word does not provide a mechanism for doing this within its interface, however there is a tool available that does do this work for you even though it’s interface is relatively crude.  This tool is called the Word 2007 Content Control Toolkit and is available via the following link (Word 2007 Content Control Toolkit).  Although this is a relatively simple interface it does allow for the creation of custom parts and the binding of form fields to those parts.  It also provides all the source code for the application so it is possible to identify how the custom part was constructed and how binding is defined.

Building the Custom Part

Before using the Word 2007 Content Control Toolkit it is advisable to create an XML document containing a static version of the XML that is to be used in the document.  The reason for this is that although this tool does provide users with the ability to build XML it does not have all the full validation and UI capabilities of a true XML creation document.  My preference is to use Microsoft’s Web Developer Express 2008.  This software is available, free of charge from Microsoft (Microsoft Express Products).  Using this tool I created a static version of the XML defined in a previous post (the custom part) and saved the file to a local directory.

Binding the Custom Part to the Fields
Once an xml document has been created the toolkit can be used to load its structure into the Complaint Record document created in Word.  Once the toolkit has been installed and opened it is necessary to point it at the Word document that contains the form layout and will contain the custom part.  This is done by simply opening the File using File Open.  Once opened the toolkit examines the document for any form fields and lists them In the window on the left (as shown in the following diagram).

Image001

The second step is create a new Custom Part.  This is done by clicking on the Create a new Custom XML Part in the bottom right corner of the dialog. Once done it is possible to load the previously created XML document (this means that the elements and attributes do not have to be created manually).  This is done by clicking on the Open XML document button.  As a result of these actions your dialog should look similar to the following:

Image002

The last step is to bind the form fields listed on the left side with the XML content on the right side.  This is done by switching the left side from Edit view to Bind view.  Bind view provides a hierarchical representation of the XML loaded into the custom part clear depicting elements and attributes.  To perform a binding perform the following steps.

Click on an element or attribute from the binding view

Click and Drag the element to the relevant field on the left side

It is important that the click and the click and drag are two separate actions, if they are done as one the wrong element/attribute from the XML is assigned to the field.  When the item is dropped onto the form field the XPath of that element is updated.  Check this to make sure it is correct.  Once all the bindings have been created the dialog should look similar to the one below:

Image003

Once all the changes have been made it is possible to save the Word Document.  Once this has been done it is possible to open the file in Word.  If the XML part loaded has sample information within it then the document will show the fields populated.  In this case it should look similar to the image below:

Image004