InfoPath is designed for easy handling of electronical forms. Used in conjunction with SharePoint you have a powerful tool to quickly collect complex data. Although electronical forms are most suitable for collaboration of team members there is still an interface lack. Today you still can not do without paper forms and documents. To secure a contract or agreement, legally, you need the personal signature of the parties.
Which is your workaround? In case you need for example a contract with your customer you would probably design a electronical form to collect the customer’s data and contract details and another form or document which contains the contract text and some space for manual signatures. Also very often you will transfer the data from the electronical form to your paper document manually.
Task
You need a tool which automatically transforms a InfoPath 2007 form into a Word 2007 document.
Possible Solutions
- With InfoPath you can design a print view and send it to your printer. The disadvantage is that you will get a html output with all the difficulties of formatting, specially when your field sizes and text sizes are of variable lengths.
- InfoPath 2003 SDK includes the “InfoPath to Word Wizard”. You can still use this tool for InfoPath 2007. The wizard helps to create a xsl stylesheet which can be uploaded to InfoPath as a print view for Word. When you print your form it is first transformed to Word and then output. The disadvantage: The transformation is still to html output only shown by Word. Again you need manual formatting for a final result.
- Word 2007 implements the OpenXml standards which means you easily can create a document programmatically. Downloading the OpenXml SDK 2.0 gives you all the tools and libraries necessary. Disadvantage: Well, if you have not only one form to convert but many or if you have changing forms there are a lot of programming works and updates administration.
- You may follow this block to see how you can merge InfoPath 2007 form data (.xml) and a Word 2007 template (.dotx) to a resulting Word 2007 document (.docx). Furthermore the final document will be attached to the InfoPath form itself to have both forms available the same time on your SharePoint. If you need to print and sign the document simply download it, print it, sign it, scan it and upload the signed version as a .pdf file to the InfoPath form again. A piece of C# code assures that your signed document is not lost.
Solution #4: Here we go
There are some milestones to achieve until you will have the final solution. The result will be a little C# class library with embedded resources which will be used by InfoPath. We need:
- an xslt stylesheet to transform InfoPath 2007 form data into an OpenXml compatible xml file which can be loaded into a Word 2007 package. Remember Word 2007 is actually a zip archive. Rename a document to *.zip and discover what is inside.
- an additional stylesheet to preformat the form data. This is necessary to provide number, percentage, date and time formatting prior to the Word transformation.
- a little piece of C# which reads the Word template, examines the InfoPath solution for formatting purpose, retrieves the form data, creates the Word document and saves the result as an InfoPath file attachement.
Beginning with the first step
Below please find the xslt stylesheet which transforms InfoPath form data to Word document based on a Word template which is passed as a parameter. To test the stylesheet:
- Create an InfoPath 2007 form
- Fill the form with sample data and save it
- Create a Word 2007 template (*.dotx)
- Attach the form’s schema to the Word template
- Open the word template
- Select the developer tab (Maybe you need to enable it first)
- In the Xml section, select Schema
- Click Add Schema button
- Navigate to the sample form and select it. Click to the Open button
- Click OK to close Schema settings and OK to close the Templates and Add-ins windows
- Select Structure from the XML section in the ribbon to view the available elements that are defined by the schema.
- Insert all data elements you need into your Word template.
- Finish and save your Word template
- Now rename template from *.dotx to *.dotx.zip
- Copy the file word/document.xml from the zip archive. -> You got the input parameter for the stylesheet
- Rename your template back to *.dotx
- Launch Visual Studio 2005 or 2008
- Load the InfoPath form data, the template document.xml and the stylesheet (see code below)
- Use the stylesheet debugger to examine and debug it.
When transforming InfoPath to Word a resulting xml file is generated. You may use it to replace the orginal document.xml manually within the Word package (as shown above, steps 7-9).
The following posts will show how to do it programmatically.
Conclusion
The stylesheet InfoPath2Word.xslt listed below is used to convert InfoPath 2007 form data to Word 2007. A Word 2007 template is used as input parameter for the stylesheet. Both InfoPath and Word must be based on the same xml schema to successfully merge data and template to a new document.
InfoPath2Word.xslt
<?xml version="1.0" encoding="utf-8"?>
<!--
This stylesheet merges InfoPath 2007 form data with the appropriate
document.xml file of a Word 2007 document (docx) or template (dotx)
package.
The word document must have the same structure as the InfoPath
form data, which means that a data schema has to be added to the
Word document and data elements defined by the schema are
inserted into the document. You can achieve this by using the
developer tools of word. Inserting elements creates w:customXml
tags within the document which are used to synchronize with the
form data.
-->
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
exclude-result-prefixes="msxsl"
>
<!-- ======== -->
<!-- Settings -->
<!-- ======== -->
<xsl:output method="xml" indent="yes" encoding="utf-8" standalone="yes" />
<!-- ========== -->
<!-- Parameters -->
<!-- ========== -->
<!--
The input parameters are preset for debugging purpose. For runtime
use only one parameter either wordTemplateLocation or wordTemplate
-->
<xsl:param name="wordTemplateLocation" select="'Sample/document.xml'" />
<xsl:param name="wordTemplate" select="document($wordTemplateLocation)" />
<!-- ==== -->
<!-- Root -->
<!-- ==== -->
<!-- Passed InfoPath form data processing starts here -->
<xsl:template match="/">
<xsl:choose>
<xsl:when test="$wordTemplate">
<xsl:apply-templates select="$wordTemplate/*" mode="copy">
<xsl:with-param name="data" select="." />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">Parameter "wordTemplate" not passed</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- ==== -->
<!-- copy -->
<!-- ==== -->
<!-- General word elements output -->
<xsl:template match="*" mode="copy">
<xsl:param name="data" />
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="*" mode="copy">
<xsl:with-param name="data" select="$data" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- Document text output -->
<xsl:template match="w:t" mode="copy">
<xsl:copy-of select="." />
</xsl:template>
<!-- Repeated data elements output -->
<xsl:template match="w:tr[w:customXml[w:tc]]" mode="copy">
<xsl:param name="data" />
<xsl:variable name="children" select="*" />
<xsl:variable name="attrs" select="@*" />
<xsl:for-each select="$data">
<w:tr>
<xsl:copy-of select="$attrs" />
<xsl:apply-templates select="$children" mode="copy">
<xsl:with-param name="data" select="." />
</xsl:apply-templates>
</w:tr>
</xsl:for-each>
</xsl:template>
<!-- Simple empty customXml -->
<xsl:template match="w:customXml[count(*)=0]" mode="copy">
<xsl:param name="data" />
<xsl:variable name="element" select="@w:element" />
<xsl:copy>
<xsl:copy-of select="@*" />
<!--Inserting form data is done here -->
<xsl:apply-templates select="$data/*[local-name()=$element]/text()" mode="data">
<xsl:with-param name="word" select="." />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- Basic customXml with some formatting, typically w:tc or w:p elements-->
<xsl:template match="w:customXml[count(*)=1]" mode="copy">
<xsl:param name="data" />
<xsl:variable name="element" select="@w:element" />
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="*" mode="insert">
<xsl:with-param name="data" select="$data/*[local-name()=$element]" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- Nested, but not repeated customXml -->
<xsl:template match="w:customXml[.//w:customXml]" mode="copy" priority="1">
<xsl:param name="data" />
<xsl:variable name="element" select="@w:element" />
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="*" mode="copy">
<xsl:with-param name="data" select="$data/*[local-name()=$element]" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- ====== -->
<!-- insert -->
<!-- ====== -->
<xsl:template match="*" mode="insert">
<xsl:param name="data" />
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="*" mode="insert">
<xsl:with-param name="data" select="$data" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="w:p[not(w:r)]" mode="insert">
<xsl:param name="data" />
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="*" mode="insert">
<xsl:with-param name="data" select="$data" />
</xsl:apply-templates>
<!-- Insert form data into paragraph element w:p -->
<xsl:apply-templates select="$data/text()" mode="data">
<xsl:with-param name="word" select="." />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- ==== -->
<!-- data -->
<!-- ==== -->
<!-- Multiline form data -->
<xsl:template match="text()[contains(., '&#10;')]" mode="data">
<xsl:param name="word" />
<xsl:apply-templates select="msxsl:node-set(substring-before(., '&#10;'))/text()" mode="data">
<xsl:with-param name="word" select="$word" />
</xsl:apply-templates>
<xsl:apply-templates select="msxsl:node-set(substring-after(., '&#10;'))/text()" mode="data">
<xsl:with-param name="word" select="$word" />
<xsl:with-param name="break" select="true()" />
</xsl:apply-templates>
</xsl:template>
<!-- Single line form data -->
<xsl:template match="text()" mode="data">
<xsl:param name="word" />
<xsl:param name="break" select="false()" />
<w:r w:rsidRPr="{$word/ancestor::w:p/@w:rsidRPr}">
<xsl:copy-of select="$word/ancestor::w:p/w:pPr/w:rPr"/>
<xsl:if test="$break">
<w:br />
</xsl:if>
<w:t>
<xsl:value-of select="." />
</w:t>
</w:r>
</xsl:template>
</xsl:stylesheet>