Chapter 6. Using external XML data in documents Word Script Developer Task

  • Complete XML documents

  • XML search results

  • Web services

  • Automatic event-based updates

Word can access external data and insert it in your XML documents. You can accomplish tasks such as looking up an address in an external XML document, importing canned text, or getting information about a ZIP code from a Web service.

This chapter will show you how to:

  • Access XML document sources from a file system or with a URL

  • Get data from Web services using a SOAP interface

We’ll also explain how to refresh external data automatically when changes are made to the document.

Skills required

Skills required

Some knowledge of Visual Basic for Applications (VBA) and the Word object model. The section on Web services assumes a basic knowledge of Web services. For more information on Web services, see Chapter 19, “Web services introduction”, on page 414 and Chapter 23, “Web services technologies”, on page 484.

External XML documents

Worldwide Widget Corporation is known for its award-winning customer service. It responds to all customer complaints with a sincere personalized form letter. Thanks to XML, the letter is largely computer-generated.

Figure 6-1 shows how a typical letter looks when rendered by Word.

Typical customer letter, rendered by Word

Figure 6-1. Typical customer letter, rendered by Word

The XML representation is shown in Example 6-1 and its schema in Example 6-2.

Example 6-1. Typical customer letter (letter.xml)

<?xml version="1.0" encoding="UTF-8"?>
<customerLetter xmlns="http://xmlinoffice.com/letter">
  <date>2004-04-15</date>
  <address>
    <line>123 Front Street</line>
    <city>Traverse City</city>
    <state>Michigan</state>
    <zip>49684</zip>
  </address>
  <greeting> Dear <salutation>Mr.</salutation> <customer id=
     "C12266">Steve Jackson</customer>:</greeting>
  <body type="problem">
<p>Thank you for your recent <format>letter</format> to our
Customer Service department. We apologize for the difficulties
you had with <problem>ordering from our Web site</problem>.
We take your concerns seriously and will address them
promptly.</p>
<p>We appreciate your business and hope to retain you as a
customer.</p>
 </body>
 <footer>Sincerely,
    <emp id="E18234">Denise Dorning</emp>
 </footer>
</customerLetter>

Example 6-2. Customer letter schema (letter.xsd)

<?xml version="1.0"?>
<xs:schema targetNamespace="http://xmlinoffice.com/letter"
           xmlns="http://xmlinoffice.com/letter"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified">
 <xs:element name="letter" type="LetterType"/>
 <xs:complexType name="LetterType">
  <xs:sequence>
   <xs:element name="date" type="xs:date"/>
   <xs:element name="address" type="AddressType"/>
   <xs:element name="greeting" type="GreetingType"/>
   <xs:element name="body" type="BodyType"/>
   <xs:element name="footer" type="FooterType"/>
  </xs:sequence>
  <xs:attribute name="id" type="xs:ID"/>
  <xs:attribute name="type" type="xs:string"/>
 </xs:complexType>
 <xs:complexType name="AddressType"><xs:sequence>
   <xs:element name="line" type="xs:string" maxOccurs="unbounded"/>
   <xs:element name="city" type="xs:string"/>
   <xs:element name="state" type="xs:string"/>
   <xs:element name="zip" type="xs:string"/>
 </xs:sequence></xs:complexType>
 <xs:complexType name="GreetingType" mixed="true"><xs:sequence>
    <xs:element name="salutation" type="xs:string"/>
    <xs:element name="customer" type="IdentifiedType"/>
 </xs:sequence></xs:complexType>
 <xs:complexType name="BodyType">
  <xs:sequence>
   <xs:element name="p" type="ParaType" maxOccurs="unbounded"/>
  </xs:sequence>
  <xs:attribute name="type" type="xs:string"/>
 </xs:complexType>
 <xs:complexType name="ParaType" mixed="true">
  <xs:choice minOccurs="0" maxOccurs="unbounded">
   <xs:element name="format" type="xs:string"/>
   <xs:element name="problem" type="xs:string"/>
  </xs:choice>
 </xs:complexType>
 <xs:complexType name="FooterType" mixed="true"><xs:sequence>
   <xs:element name="emp" type="IdentifiedType"/>
 </xs:sequence></xs:complexType>
 <xs:complexType name="IdentifiedType">
  <xs:simpleContent><xs:extension base="xs:string">
   <xs:attribute name="id" type="xs:string"/>
   </xs:extension></xs:simpleContent>
  </xs:complexType>
</xs:schema>

The letter has XML elements representing the customer’s name and address, the nature of the problem, the name of the employee who generated the letter, and so on. Because of the self-describing markup, these data elements can easily be manipulated, validated or completed from an external data source.

For example, Worldwide’s system automatically fills in a customer’s address based on its name, imports canned text from an external document, and automatically fills in the city and state based on the ZIP code.

Importing an external XML document

Note from Example 6-1 that the body element has a type attribute:

<body type="problem">

The plan is that the letter reply system will eventually support a variety of body types. For each type there would be a separate XML file with appropriate canned text.

For now there is just the XML document shown in Example 6-3, which is to be included in every customer “problem” letter. It contains a paragraph to be embedded directly in the body of the letter, with placeholder text for the format and problem elements to be replaced by the human sending the letter.

Example 6-3. Canned letter paragraph (problem_para.xml)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p xmlns="http://xmlinoffice.com/letter">Thank you for your recent
<format>ENTER FORMAT</format> to our Customer Service department.
We apologize for the difficulties you had with <problem>ENTER
PROBLEM</problem>. We take your concerns seriously and will
address them promptly.</p>

You can set up your Word document so that when a body element is inserted, it is automatically populated with the contents of the problem_para.xml document. The VBA function shown in Example 6-4 accomplishes this.[1]

Example 6-4. Function to automatically insert external XML document

Private Sub Document_XMLAfterInsert(ByVal NewXMLNode
As XMLNode, ByVal InUndoRedo As Boolean)
    If NewXMLNode Is Nothing Then
        'do nothing
    ElseIf NewXMLNode.BaseName = "body" Then
        Dim theRange As Word.Range
        Set theRange = NewXMLNode.Range
        theRange.InsertFile "C:problem_para.xml"
    End If

End Sub

This function is automatically executed in response to the event XMLAfterInsert, which occurs every time an XML element is added to the letter document. On line 5 it checks whether the newly inserted element is of type body. If so, it creates a range variable theRange and assigns it the range of the newly inserted body element. It then inserts the contents of problem_para.xml into the range. (In the future, it will test the value of the type attribute to determine which external XML document to insert.)

A working version of this code can be found in the example file letter insert.doc.

Tip

Tip

The inserted canned text need not be an XML document in a file system. It could also be returned from a URL reference. For example, Worldwide Widget has a customer information application that returns a customer’s address from, for example: http://xmlinoffice.com/customerlookup?id=123456. The returned XML can be inserted exactly as if it was a local file. The important thing is the XML; any means of addressing it will work the same way.

Querying an external XML document

On the other hand, suppose you do not want to insert an entire XML document, but want to look up a value in an external XML document. For example, you might have some customer names and addresses stored in an XML document that looks like the one in Example 6-5.

Example 6-5. External customer address file (customers.xml)

<?xml version="1.0" encoding="UTF-8"?>
<customers>
  <customer>
    <name>Steve Jackson</name>
    <salutation>Mr.</salutation>
    <address>123 Front Street</address>
    <city>Traverse City</city>
    <state>Michigan</state>
    <zip>49684</zip>
 </customer>
  <customer>
    <name>Brenda Smith</name>
    <salutation>Ms.</salutation>
    <address>5 Wilson Boulevard</address>
    <city>Arlington</city>
    <state>Virginia</state>
    <zip>22203</zip>
 </customer>
 <!-- etc. -->
</customers>

When a document author enters the customer name in the customer element, the document should automatically look up the customer’s address in the customers.xml file and complete it in the letter. The function shown in Example 6-6 will do this.

Example 6-6. Function to automatically update address based on customer name

Sub GetAddress()

    Const xmlnsLetter = "xmlns:let='http://xmlinoffice.com/letter'"
    Dim str_customer, str_line, str_city, str_state, str_zip
      As String
    Dim cust_node, line_node, city_node, state_node, zip_node
      As XMLNode

    Set line_node =
      ActiveDocument.SelectSingleNode("//let:line", xmlnsLetter)
    Set city_node =
      ActiveDocument.SelectSingleNode("//let:city", xmlnsLetter)
    Set state_node =
      ActiveDocument.SelectSingleNode("//let:state", xmlnsLetter)
    Set zip_node =
      ActiveDocument.SelectSingleNode("//let:zip", xmlnsLetter)

    'Open the customers document
    Dim custXML As MSXML2.DOMDocument50
    Set custXML = New MSXML2.DOMDocument50
    custXML.async = False
    custXML.Load ("c:customers.xml")

    'Find the appropriate customer element
    str_customer = ActiveDocument.
      SelectSingleNode("//let:customer", xmlnsLetter).Text
    Set cust_node = custXML.
      SelectSingleNode("//customer[name='" & str_customer & "']")

    'Assign values from customer.xml to letter.xml
    If cust_node Is Nothing Then
        MsgBox ("Invalid customer name " + str_customer)
        line_node.Text = "UNKNOWN"
        city_node.Text = "UNKNOWN"
        state_node.Text = "UNKNOWN"
        zip_node.Text = "UNKNOWN"
    Else
        line_node.Text = cust_node.SelectSingleNode("address").Text
        city_node.Text = cust_node.SelectSingleNode("city").Text
        state_node.Text = cust_node.SelectSingleNode("state").Text
        zip_node.Text = cust_node.SelectSingleNode("zip").Text
    End If

End Sub
  • Lines 9 through 16 assign the relevant nodes of the letter document to variables.

  • Lines 19 through 22 then access the customers.xml document and assign it to the variable custXML.

  • Lines 25 to 28 retrieve the customer name from the letter document and attempt to find a match in the customer document.

  • If the customer node is found, lines 38 through 41 assign the selected values to the relevant nodes in the letter document.

This function assumes that the line, city, state and zip elements already exist in the document. A more sophisticated function could be written to check for their existence and insert them if necessary.

Responding to an event

Rather than requiring the user to take some action to update the address, perhaps you want this function to be executed every time the user changes the customer name. To do this, you need to write a function that responds to the XMLSelectionChange event, which occurs every time the user changes the selection to a different element.

The desired function is shown in Example 6-7. It determines whether the changed XML is the content of the customer element and, if so, it calls the GetAddress function.

Example 6-7. Responding to the XMLSelectionChange event

Dim WithEvents oApp As Word.Application
Private Sub Document_Open()
    Set oApp = Application
End Sub
Private Sub oApp_XMLSelectionChange
  (ByVal Sel As Word.Selection,
   ByVal OldXMLNode As Word.XMLNode,
   ByVal NewXMLNode As Word.XMLNode,
   Reason As Long)

    If OldXMLNode Is Nothing Then
        'do nothing
    ElseIf OldXMLNode.BaseName = "customer" Then
        GetAddress
    End If

End Sub

A working version of this code can be found in the example file letter query.doc.

SOAP Web services

In this section, we will see how the letter document utilizes a Web service that, given a U.S. ZIP code, returns the city and state. To do this, we will make use of a separate toolkit provided by Microsoft for dealing with the SOAP interface.

The ZIP code Web service

First, let’s take a look at the Web service we will be using for this example. This Web service has several operations related to United States ZIP codes. The one we are going to use is called GetInfoByZIP. Given a ZIP code, this operation returns the city, state, area code and time zone.

The WSDL description of this Web service can be found at: http://www.webservicex.net/uszip.asmx?WSDL

Within the WSDL document, we can see the operation we want. It is shown in Example 6-8.

Example 6-8. WSDL description of a Web service operation

<operation name="GetInfoByZIP">
  <documentation>Get State Code, City, Area Code, Time Zone,
Zip Code by Zip Code</documentation>
  <input message="s0:GetInfoByZIPSoapIn" />
  <output message="s0:GetInfoByZIPSoapOut" />
</operation>

The operation definition references the input and output messages that are shown in Example 6-9.

Example 6-9. Web service input and output messages

<message name="GetInfoByZIPSoapIn">
  <part name="parameters" element="s0:GetInfoByZIP" />
</message>
<message name="GetInfoByZIPSoapOut">
  <part name="parameters" element="s0:GetInfoByZIPResponse" />
</message>

The input and output messages, in turn, refer to the types of element that are sent to and returned from the Web service. An excerpt from the schema definition that declares them is shown in Example 6-10.

Example 6-10. Declarations of message element types

<s:element name="GetInfoByZIP">
  <s:complexType>
    <s:sequence>
      <s:element minOccurs="0" maxOccurs="1"
                 name="USZip" type="s:string" />
    </s:sequence>
  </s:complexType>
</s:element>
<s:element name="GetInfoByZIPResponse">
  <s:complexType>
    <s:sequence>
      <s:element minOccurs="0" maxOccurs="1"
                 name="GetInfoByZIPResult">
        <s:complexType mixed="true">
          <s:sequence>
            <s:any />
          </s:sequence>
        </s:complexType>
      </s:element>
    </s:sequence>
  </s:complexType>
</s:element>

According to the schema for the input message, the operation expects to receive a GetInfoByZIP element, which in turn contains a USZip element that contains a string: the ZIP code.

The structure of the output message is unknown, since the schema for the output message does not constrain the content of the GetInfoByZIPResult element. However, by running a test we find that the output looks like Example 6-11.

Example 6-11. XML document returned by ZIP code Web service

<ws:GetInfoByZIPResponse xmlns:ws="http://www.webserviceX.NET">
  <ws:GetInfoByZIPResult>
    <NewDataSet>
      <Table>
        <CITY>Traverse City</CITY>
        <STATE>MI</STATE>
        <ZIP>49684</ZIP>
        <AREA_CODE>616</AREA_CODE>
        <TIME_ZONE>E</TIME_ZONE>
      </Table>
    </NewDataSet>
  </ws:GetInfoByZIPResult>
</ws:GetInfoByZIPResponse>

Both the input and output messages are contained in SOAP wrappers when transmitted. Parsing and generating SOAP is best left to specialized software, which is available for the Office products.

The Office Web Services Toolkit

To simplify the use of SOAP Web services with Office, Microsoft provides a separate, downloadable toolkit. The Office Web Services Toolkit frees the developer from generating and parsing SOAP messages, and even from constructing the XML fragments that contain the data sent to and received from the Web service.

For each service, the toolkit generates a class (or classes) that has a method for each of the service’s operations. Application code can then be written to access these simplified classes.

At the time of writing, the current version of the toolkit is called Office XP Web Services Toolkit 2.0. It can be downloaded from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnxpwst/html/odc_wstoolkitoverview.asp.[2]

The toolkit contains documentation and examples, and a tool called the Web Services Reference Tool that is integrated with the Visual Basic Editor.[3]

Using the Web Services Reference Tool

The Web Services Reference Tool allows you to generate classes that you can use to access a particular Web service. You can search for a Web service based on keywords, or specify a Web service by its URL.

To launch the Web Services Reference Tool, first open the document from which you will be calling the Web service. To use our form letter example:

  1. Open the document letter.xml.

  2. On the Tools menu, point to Macro, then click Visual Basic Editor. This will open the Visual Basic Editor.

  3. Launch the Web Services Reference Tool by clicking Web Services References on the Tools menu. This brings up the main dialog shown in Figure 6-2.

    The Web Services Reference Tool

    Figure 6-2. The Web Services Reference Tool

  4. In our case, we already know the particular Web service we want to use, so we can go directly to it. Click Web Service URL on the left side of the dialog.

  5. Type the Web service location http://www.webservicex.net/uszip.asmx?WSDL into the URL box and click Search.

  6. This will bring up the name of the Web service and its operations on the right side of the dialog, as shown in Figure 6-3.

    The Web Services Reference Tool showing available services and operations

    Figure 6-3. The Web Services Reference Tool showing available services and operations

  7. Select the USZip service by checking the box next to it and click Add. This will generate the necessary class in the Visual Basic Editor.

Working with the generated Web service class

The generated class named clsws_USZip represents the Web service. It has a method called wsm_GetInfoByZIP, which corresponds to the GetInfoByZIP operation of the Web service. You can write a GetCityState method that calls the wsm_GetInfoByZIP method, as shown in Example 6-12.

Example 6-12. GetCityState method

Sub GetCityState()

Const xmlnsLetter =
  "xmlns:let='http://xmlinoffice.com/letter'"
Dim objResolver As clsws_USZip
Set objResolver = New clsws_USZip
Dim returnedNodes As MSXML2.IXMLDOMNodeList
Dim str_city As String
Dim str_state As String
Dim city_node As XMLNode
Dim state_node As XMLNode
Dim zip_node As XMLNode

Set city_node =
  ActiveDocument.SelectSingleNode("//let:city", xmlnsLetter)
Set state_node =
  ActiveDocument.SelectSingleNode("//let:state", xmlnsLetter)
Set zip_node =
  ActiveDocument.SelectSingleNode("//let:zip", xmlnsLetter)

Set returnedNodes = objResolver.wsm_GetInfoByZIP(zip_node.Text)

str_city = returnedNodes.Item(0).SelectSingleNode("//CITY").Text
str_state = returnedNodes.Item(0).SelectSingleNode("//STATE").Text
city_node.Text = str_city
state_node.Text = str_state

End Sub
  • Lines 3 through 12 define all the constants and variables used.

  • Lines 14 through 19 locate the city, state and ZIP nodes in our letter document, which are relevant to the Web service.

  • Line 21 calls the wsm_GetInfoByZIP method, passing it the ZIP code. Rather than returning the whole SOAP message that contains the document shown in Example 6-11, the method extracts the relevant returned data, namely the NewDataSet element. This is represented as a variable called returnedNodes, which is a list of nodes of type IXMLDOMNodeList.

  • Lines 23 and 24 assign the values of the CITY and STATE elements to two string variables.

  • Lines 25 and 26 then assign these string values to the appropriate nodes in our letter document.

Warning

Warning

The Office Web Services toolkit is very useful for simple examples like this one. It has a number of limitations that prevent it from working without modifications on all Web services. For example, it can’t handle element-type names that are also Visual Basic reserved words, and it doesn’t support extensions in the XML Schema types defined in the WSDL. However, it can generate a starting point, which you can tweak for your Web service.

Responding to two variations of an event

In 6.1.3, “Responding to an event”, on page 121 we saw how external data could be updated automatically when a corresponding field in the document was changed. The technique works the same way when the external data is provided by a Web service.

In fact, you can use the same code as we did in Example 6-15. The only difference is that you would replace:

Example 6-13. Test for change in customer

ElseIf OldXMLNode.BaseName = "customer" Then
    GetAddress

with:

Example 6-14. Test for change in zip

ElseIf OldXMLNode.BaseName = "zip" Then
    GetCityState

If you wanted automatic updates for both situations, you could use both tests at once, as shown in Example 6-15.

Example 6-15. Responding to two kinds of XMLSelectionChange event

Dim WithEvents oApp As Word.Application

Private Sub Document_Open()
    Set oApp = Application
End Sub

Private Sub oApp_XMLSelectionChange
  (ByVal Sel As Word.Selection,
   ByVal OldXMLNode As Word.XMLNode,
   ByVal NewXMLNode As Word.XMLNode,
   Reason As Long)

    If OldXMLNode Is Nothing Then
        'do nothing
    ElseIf OldXMLNode.baseName = "customer" Then
        GetAddress
    ElseIf OldXMLNode.BaseName = "zip" Then
        GetCityState
    End If

End Sub

After saving this project and closing and reopening the letter.xml document, you will find that every time you change the ZIP code, the city and state will automatically be updated. Similarly, every time you change the customer its address will be updated.



[1] All of the examples in this chapter are written in Visual Basic for Applications (VBA).

[2] Although the name of the toolkit implies that it is only for Office XP, it also works with Office 2003 and its name may be changed to reflect this fact.

[3] Installation of the toolkit is in two parts. You must first install the toolkit itself, from the downloaded compressed file. The Web Services Reference Tool is then installed separately from the setup.exe executable file in the directory where you installed the toolkit.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.119.206