Contents


Processing WSDL documents with XSLT

Tips and tricks for transforming Web service WSDL documents using XSLT stylesheets

Comments

In the world of Web services, Web Services Description Language (WSDL) documents describe the interfaces provided by Web services -- and indicate where instances of those Web services can be found on the network. In general, WSDL documents are composed and consumed with the aid of various development tools. As with virtually all Web service related information, WSDL documents are expressed as standard XML documents.

One of the strengths of XML is the availability of increasingly powerful tools to process the data. One such tool, XSLT, can be used to effect complex transformations on XML data. Since WSDL documents are expressed as XML, it is possible to automate a wide variety of WSDL conversion tasks using XSLT. This article describes some of the approaches that have proven useful in processing WSDL documents with XSLT (and some that haven't) and lists some general techniques that can be applied to simplify automated processing of WSDL documents.

This document assumes a working knowledge of both WSDL and XSLT. The following links on IBM developerWorks offer useful background information:

SAX and DOM versus XSL

There are two basic approaches to modifying a WSDL document. The first uses a language such a Java or C and an XML-parsing API such as SAX or DOM. The original WSDL document is read, its contents parsed and modified by the algorithm and the modified WSDL document constructed -- either by manually assembling the document out of strings or by using a language-specific XML library. The second approach utilizes an XSL transform that specifies how the input WSDL document should be manipulated to produce the output WSDL document. While many of the techniques described here apply to the first approach, the XML approach has several advantages:

  1. General-purpose languages have no special constructs for dealing with XML documents. While it is certainly possible to use them for such tasks, it requires much more effort to achieve equivalent results.
  2. With a general-purpose language, the concepts behind the transformation get lost in the sheer volume of code necessary to achieve it. The relative terseness of the XSL transform makes the underlying concepts more apparent and thus easier to debug and maintain.
  3. XSL transforms can be processed by a number of different tools, many of which are already widely available. For example, the ant build tool, the libxslt package available on most Linux distributions, the Java runtime environment and many Web browsers can execute XSL transforms.

For completeness, it should be noted that XSL is somewhat weak in its ability to express algorithms that are not directly related to the structure of the input XML document. If the needed transformations make heavy use of such algorithms, a general-purpose language is likely to be a better choice than XSL.

Challenges of WSDL

Extracting structure from a WSDL document using XSL results in some interesting challenges. To allow use of the widest possible set of XSL transform engines, restrict stylesheets to features defined in XSL version 1.1. Some of the features added in XSL version 2.0 will simplify some of the XSL shown here, but will not be prevalent in many environments for some time.

The largest obstacle in processing WSDL documents with XSL is the extensive use of namespace prefixes within attribute values in WSDL documents. These namespace prefixes are used to correlate messages, portTypes, bindings and ports. While XSL is adept at handling namespaces on both elements and attributes in the input document, its constructs for dealing with them inside attribute values and character data are somewhat weak.

Relationships within a WSDL document

While it is not possible to fully describe the structure of a WSDL document here, it is worth describing a few of the salient relationships among elements of the WSDL document. Correct manipulation of the WSDL document depends on understanding and preserving these relationships.

Figure 1. Relationship of WSDL document elements
Relationship of WSDL document elements
Relationship of WSDL document elements

Each <wsdl:message>, <wsdl:portType> and <wsdl:binding> element contains a required "name" attribute of type ncname, but is referenced from elsewhere in the WSDL document using a qname. The URIs associated with the qnames are inherited from the targetNamespace attribute on the <wsdl:definitions> element. If the <wsdl:definitions> element does not specify a targetNamespace, the qname inherits a null namespace URI. This implicit namespace qualification is shown by dashed gray arrow in figure 1.

The following additional "interesting" relationships exist among the elements in a WSDL document. Each is shown with a solid black arrow in figure 1.

  • Within the <wsdl:operation>s defined in a <wsdl:portType>, each <wsdl:input>, <wsdl:output> and <wsdl:fault> element refers to a <wsdl:message> via its message attribute.That is:
    wsdl:portType/wsdl:operation/wsdl:input/@message -> wsdl:message
    wsdl:portType/wsdl:operation/wsdl:output/@message -> wsdl:message
    wsdl:portType/wsdl:operation/wsdl:fault/@message -> wsdl:message
  • The "type" attribute on a <wsdl:binding> element refers to a <wsdl:portType> element. That is:
    wsdl:binding/@type -> wsdl:portType
  • Within a <wsdl:binding> element, each <wsdl:operation> has a "name" attribute that equals the "name" attribute on a <wsdl:operation> in the associated <wsdl:portType>.
    wsdl:binding/wsdl:operation/@name = wsdl:portType/wsdl:operation/@name
  • Within the <wsdl:operation>s defined in a <wsdl:binding>, each <wsdl:input>, <wsdl:output> and <wsdl:fault> element may contain a name attribute whose value matches a name attribute on the corresponding element in the <wsdl:portType>. That is:
    wsdl:binding/wsdl:operation/wsdl:input/@name =
      wsdl:portType/wsdl:operation/wsdl:input/@name
    
    wsdl:binding/wsdl:operation/wsdl:output/@name =
      wsdl:portType/wsdl:operation/wsdl:output/@name
    
    wsdl:binding/wsdl:operation/wsdl:fault/@name = 
      wsdl:portType/wsdl:operation/wsdl:fault/@name

Techniques

What follows are several techniques and XSL snippets that have proven useful in processing WSDL documents. Many of these approaches could be generalized to work with non-WSDL XML documents as well.

In addition to an XSL snippet, each technique described below includes a simple example stylesheet showing how the technique can be used to process a WSDL document. For each example stylesheet, the output of applying the stylesheet to the following contrived WSDL document is also shown:

Listing 1. test.wsdl used in examples
<?xml version="1.0" encoding="UTF-8"?>

<!-- 

This file contains an imaginary WSDL used to illustrate how various
types of XSL transformations can be applied to it.

-->

<wsdl:definitions name="test"
  targetNamespace="http://tempuri.org/test/"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:test="http://tempuri.org/test/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  >

  <wsdl:message name="inputmsg1">
    <wsdl:part name="inputmsg1-part" type="xsd:string"/>
  </wsdl:message>

  <wsdl:message name="inputmsg2">
    <wsdl:part name="inputmsg2-part" type="xsd:decimal"/>
  </wsdl:message>

  <wsdl:message name="inputmsg3">
    <wsdl:part name="inputmsg3-part" type="xsd:integer"/>
  </wsdl:message>

  <wsdl:message name="outputmsg">
    <wsdl:part name="outputmsg-part" type="xsd:base64Binary"/>
  </wsdl:message>
    
  <wsdl:message name="faultmsg"/>
    
    
  <wsdl:portType name="porttype">
    <!-- one-way operation -->
    <wsdl:operation name="porttype-op1">
       <wsdl:input message="test:inputmsg1" name="porttype-op1-input"/>
    </wsdl:operation>

    <!-- request-response operation -->
    <wsdl:operation name="porttype-op2">
       <wsdl:input message="test:inputmsg2" name="porttype-op2-input"/>
       <wsdl:output message="test:outputmsg" name="porttype-op2-output"/>
       <wsdl:fault message="test:faultmsg" name="porttype-op2-fault"/>
    </wsdl:operation>

    <!-- solicit-response operation -->
    <wsdl:operation name="porttype-op3">
       <wsdl:output message="test:outputmsg" name="porttype-op3-output"/>
       <wsdl:input message="test:inputmsg3" name="porttype-op3-input"/>
       <wsdl:fault message="test:faultmsg" name="porttype-op3-fault"/>
    </wsdl:operation>

    <!-- notification operation -->
    <wsdl:operation name="porttype-op4">
       <wsdl:output message="test:outputmsg" name="porttype-op4-output"/>
    </wsdl:operation>
  </wsdl:portType>


  <wsdl:binding name="binding" type="test:porttype">
    <soap:binding style="document"
      transport="http://schemas.xmlsoap.org/soap/http"/>

    <wsdl:operation name="porttype-op1">
      <soap:operation soapAction="http://tempuri.org/test/soapAction"/>

      <wsdl:input name="porttype-op1-input">
        <soap:body use="literal" parts="inputmsg1-part"/>
      </wsdl:input>
    </wsdl:operation>

    <wsdl:operation name="porttype-op2">
      <soap:operation soapAction="http://tempuri.org/test/soapAction"/>

      <wsdl:input name="porttype-op2-input">
        <soap:body use="literal" parts="inputmsg2-part"/>
      </wsdl:input>

      <wsdl:output name="porttype-op2-output">
        <soap:body use="literal" parts="outputmsg-part"/>
      </wsdl:output>

      <wsdl:fault name="porttype-op2-fault">
        <soap:fault name="porttype-op2-fault" use="literal"/>
      </wsdl:fault>
    </wsdl:operation>

    <wsdl:operation name="porttype-op3">
      <soap:operation soapAction="http://tempuri.org/test/soapAction"/>

      <wsdl:input name="porttype-op3-input">
        <soap:body use="literal" parts="inputmsg3-part"/>
      </wsdl:input>

      <wsdl:output name="porttype-op3-output">
        <soap:body use="literal" parts="outputmsg-part"/>
      </wsdl:output>

      <wsdl:fault name="porttype-op3-fault">
        <soap:fault name="porttype-op3-fault" use="literal"/>
      </wsdl:fault>
    </wsdl:operation>

    <wsdl:operation name="porttype-op4">
      <soap:operation soapAction="http://tempuri.org/test/soapAction"/>

      <wsdl:output name="porttype-op4-output">
        <soap:body use="literal" parts="outputmsg-part"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>


  <wsdl:service name="service">
    <wsdl:port name="service-port" binding="test:binding">
      <soap:address location="http://localhost:9080/not/used"/>
    </wsdl:port>
  </wsdl:service>

</wsdl:definitions>

Technique 1: Map a namespace prefix to a namespace URI

Many WSDL elements define attributes of type qname. Since the mapping of namespace prefixes to URIs is not required to be unique or consistent even within a single document, any robust WSDL parsing logic must be able to translate the namespace prefixes in qnames to namespace URIs. The XSL namespace-uri() function maps a namespace prefix to a namespace URI, but only understands nodes in the input document. Since the namespace prefix is part of an attribute value in the WSDL case, some additional work is required.

The following XSL code translates the namespace prefix in a string to the corresponding namespace URI. If the supplied string does not contain a prefix, the namespace URI is an empty string. Placing this logic in its own template allows it to be easily invoked from anywhere, in the context of arbitrary elements.

Listing 2. Technique 1 XSL
<!-- emits the namespace uri associated with the prefix of the
     specified qname based on current()'s namespace nodes -->

<xsl:template name="namespace-uri-of-qname">
  <xsl:param name="qname"/>

  <xsl:if test="contains($qname,':')">
    <xsl:value-of select="namespace::*[name()=substring-before($qname,':')]"/>
  </xsl:if>
</xsl:template>

Here is an example of how this XSL template is used:

Listing 3. Technique 1 example
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:import href="wsdl-util.xsl"/>
  <xsl:strip-space elements="*"/>
  <xsl:output method="text"/>

  <!-- print out the namespace URIs associated with input messages -->
  <xsl:template match="wsdl:input[@message]">

    <xsl:text>prefix of </xsl:text>
    <xsl:value-of select="name()"/>
    <xsl:text> maps to namespace uri </xsl:text>

    <xsl:call-template name="namespace-uri-of-qname">
      <xsl:with-param name="qname" select="name()"/>
    </xsl:call-template>
    <xsl:text>
</xsl:text>

    <xsl:text>prefix of </xsl:text>
    <xsl:value-of select="@message"/>
    <xsl:text> maps to namespace uri </xsl:text>

    <xsl:call-template name="namespace-uri-of-qname">
      <xsl:with-param name="qname" select="@message"/>
    </xsl:call-template>
    <xsl:text>
</xsl:text>

  </xsl:template>

</xsl:stylesheet>

Running the above example against the test WSDL document produces:

Listing 4. Technique 1 example output
prefix of wsdl:input maps to namespace uri http://schemas.xmlsoap.org/wsdl/
prefix of test:inputmsg1 maps to namespace uri http://tempuri.org/test/
prefix of wsdl:input maps to namespace uri http://schemas.xmlsoap.org/wsdl/
prefix of test:inputmsg2 maps to namespace uri http://tempuri.org/test/
prefix of wsdl:input maps to namespace uri http://schemas.xmlsoap.org/wsdl/
prefix of test:inputmsg3 maps to namespace uri http://tempuri.org/test/

Technique 2: Map a namespace URI to a namespace prefix

The reverse problem exists when an XSL stylesheet needs to generate qnames in WSDL document attributes. That is, the XSL stylesheet knows the namespace URI, but needs to emit a namespace prefix that is mapped to that URI for the current element. The XSL transform engine would handle this automatically if the namespace were associated with an output element, but since WSDL requires it to be in an attribute value, manual processing is required. Error handling makes the XSL for this technique a bit more complex than for the first technique. Also shown in the following XSL is a special case template that emits a prefix for the WSDL document's targetNamespace URI.

Listing 5. Technique 2 XSL
<!-- emits a prefix that maps to the specified namespace uri for current() -->

<xsl:template name="prefix-for-namespace">
  <xsl:param name="namespace-uri"/>

  <xsl:if test="$namespace-uri">
    <!-- terminate if current() does not have a prefix for $namespace-uri -->
    <xsl:if test="not(namespace::*[string() = $namespace-uri])">
      <xsl:message terminate="yes">
        <xsl:text>Unable to find namespace prefix for namespace </xsl:text>
        <xsl:value-of select="$namespace-uri"/>
        <xsl:text> while processing </xsl:text>
        <xsl:value-of select="name()"/>
        <xsl:text> element.</xsl:text>
      </xsl:message>
    </xsl:if>

    <xsl:value-of select="name(namespace::*[string() = $namespace-uri])"/>
    <xsl:text>:</xsl:text>
  </xsl:if>
  
</xsl:template>


<!-- emits a prefix that maps to the wsdl:definitions/@targetNamespace -->

<xsl:template name="prefix-for-target-namespace">
  <xsl:if test="/wsdl:definitions/@targetNamespace">
    <xsl:call-template name="prefix-for-namespace">
      <xsl:with-param name="namespace-uri">
        <xsl:value-of select="/wsdl:definitions/@targetNamespace"/>
      </xsl:with-param>
    </xsl:call-template>
  </xsl:if>
</xsl:template>

Here is an example of how this XSL template is used:

Listing 6. Technique 2 example
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:import href="wsdl-util.xsl"/>
  <xsl:strip-space elements="*"/>
  <xsl:output method="text"/>

  <!-- all the namespaces are defined globally, so just pick
       an arbitrary element -->
  <xsl:template match="wsdl:definitions">

    <xsl:text>namespace uri http://www.w3.org/2001/XMLSchema</xsl:text>
    <xsl:text> mapped by prefix </xsl:text>

    <xsl:call-template name="prefix-for-namespace">
      <xsl:with-param name="namespace-uri"
                      select="'http://www.w3.org/2001/XMLSchema'"/>
    </xsl:call-template>
    <xsl:text>
</xsl:text>

    <xsl:text>targetNamepace uri mapped by prefix </xsl:text>
    <xsl:call-template name="prefix-for-target-namespace"/>
    <xsl:text>
</xsl:text>

  </xsl:template>

</xsl:stylesheet>

Running the above example against the test WSDL document produces:

Listing 7. Technique 2 example output
namespace uri http://www.w3.org/2001/XMLSchema mapped by prefix xsd:
targetNamepace uri mapped by prefix test:

Technique 3: Extract the local name from a qname

The last of the qname techniques provides an analog to theXSL local-name() function. As with the previous techniques XSL provides this function for nodes in the source document, but the fact that WSDL uses an attribute value to store the qname complicates matters.

Listing 8. Technique 3 XSL
<!-- emits the local name of the specified qname -->

<xsl:template name="local-name-of-qname">
  <xsl:param name="qname"/>
  
  <xsl:choose>
    <xsl:when test="contains($qname,':')">
      <xsl:value-of select="substring-after($qname,':')"/>
    </xsl:when>
    
    <xsl:otherwise>
      <xsl:value-of select="$qname"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Here is an example of how this XSL template is used:

Listing 9. Technique 3 example
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:import href="wsdl-util.xsl"/>
  <xsl:strip-space elements="*"/>
  <xsl:output method="text"/>

  <!-- match an arbitrary element so we can invoke the template -->
  <xsl:template match="wsdl:definitions">

    <xsl:text>local name of foo:bar is </xsl:text>

    <xsl:call-template name="local-name-of-qname">
      <xsl:with-param name="qname" select="'foo:bar'"/>
    </xsl:call-template>
    <xsl:text>
</xsl:text>

    <xsl:text>local name of baz is </xsl:text>

    <xsl:call-template name="local-name-of-qname">
      <xsl:with-param name="qname" select="'baz'"/>
    </xsl:call-template>
    <xsl:text>
</xsl:text>

  </xsl:template>

</xsl:stylesheet>

Running the above example against the test WSDL document produces:

Listing 10. Technique 3 example output
local name of foo:bar is bar
local name of baz is baz

Technique 4: Correlate portType operations with messages

When processing WSDL documents, it is often necessary to navigate from <wsdl:portType>/<wsdl:operation> elements back to the <wsdl:message> elements they refer to. Within the WSDL document, this is accomplished via the message attributes on <wsdl:input>, <wsdl:output> and <wsdl:fault> messages. While it is tempting to use simple XPath expressions to perform this navigation, this approach is not guaranteed to work in all cases. In particular, this approach does not correctly handle multi-file WSDL documents or inconsistent use of namespace prefixes within the WSDL document (both of which are perfectly legal according to the specifications).

The approach shown below uses XSL named keys in conjunction with the templates introduced in the previous techniques to build a robust solution to this problem.

Listing 11. Technique 4 XSL
<!-- index of wsdl:message by namespace uri, wsdl:message/@name -->

<xsl:key name="message"
         match="wsdl:message"
         use="concat(/wsdl:definitions/@targetNamespace,' ',@name)"/>


<!-- message key value that corresponds to a
     wsdl:portType/wsdl:operation/wsdl:* -->

<xsl:template match="wsdl:portType/wsdl:operation/wsdl:input |
                     wsdl:portType/wsdl:operation/wsdl:output |
                     wsdl:portType/wsdl:operation/wsdl:fault"
              mode="message-key">
  <xsl:call-template name="namespace-uri-of-qname">
    <xsl:with-param name="qname" select="@message"/>
  </xsl:call-template>

  <xsl:text> </xsl:text>

  <xsl:call-template name="local-name-of-qname">
    <xsl:with-param name="qname" select="@message"/>
  </xsl:call-template>
</xsl:template>

Here is an example of how this XSL template is used:

Listing 12. Technique 4 example
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:import href="wsdl-util.xsl"/>
  <xsl:strip-space elements="*"/>
  <xsl:output method="text"/>

  <!-- dump out message part counts for all operations -->
  <xsl:template match="wsdl:portType/wsdl:operation">

    <xsl:value-of select="@name"/>
    <xsl:text> has </xsl:text>


    <!-- count input parts -->

    <!-- get the correct value for the message name key -->
    <xsl:variable name="input-message-key">
      <xsl:apply-templates select="wsdl:input" mode="message-key"/>
    </xsl:variable>

    <xsl:value-of select="count(key('message',$input-message-key)/
                                wsdl:part)"/>
    <xsl:text> input parts, </xsl:text>


    <!-- count output parts -->

    <!-- get the correct value for the message name key -->
    <xsl:variable name="output-message-key">
      <xsl:apply-templates select="wsdl:output" mode="message-key"/>
    </xsl:variable>

    <xsl:value-of select="count(key('message',$output-message-key)/
                                wsdl:part)"/>
    <xsl:text> output parts, </xsl:text>


    <!-- count fault parts -->

    <!-- get the correct value for the message name key -->
    <xsl:variable name="fault-message-key">
      <xsl:apply-templates select="wsdl:fault" mode="message-key"/>
    </xsl:variable>

    <xsl:value-of select="count(key('message',$fault-message-key)/
                                wsdl:part)"/>
    <xsl:text> fault parts</xsl:text>

    <xsl:text>
</xsl:text>

  </xsl:template>

</xsl:stylesheet>

Running the above example against the test WSDL document produces:

Listing 13. Technique 4 example output
porttype-op1 has 1 input parts, 0 output parts, 0 fault parts
porttype-op2 has 1 input parts, 1 output parts, 0 fault parts
porttype-op3 has 1 input parts, 1 output parts, 0 fault parts
porttype-op4 has 0 input parts, 1 output parts, 0 fault parts

Technique 5: Correlate binding operations with portType operations

A similar problem occurs when navigating from a <wsdl:binding>/<wsdl:operation> element back to the corresponding <wsdl:portType>/<wsdl:operation> element. As with the previous technique, the XSL approach shown below uses named keys to solve the problem.

Listing 14. Technique 5 XSL
<!-- index of wsdl:portType by namespace uri, wsdl:portType/@name -->

<xsl:key name="porttype"
         match="wsdl:portType"
         use="concat(/wsdl:definitions/@targetNamespace,' ',@name)"/>


<!-- porttype key that corresponds to a wsdl:binding -->

<xsl:template match="wsdl:binding"
              mode="porttype-key">
  <xsl:call-template name="namespace-uri-of-qname">
    <xsl:with-param name="qname" select="@type"/>
  </xsl:call-template>
  
  <xsl:text> </xsl:text>

  <xsl:call-template name="local-name-of-qname">
    <xsl:with-param name="qname" select="@type"/>
  </xsl:call-template>
</xsl:template>

Here is an example of how this XSL template is used:

Listing 15. Technique 5 example
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:import href="wsdl-util.xsl"/>
  <xsl:strip-space elements="*"/>
  <xsl:output method="text"/>

  <!-- delve into each binding -->
  <xsl:template match="wsdl:binding">
    <xsl:text>binding: </xsl:text>
    <xsl:value-of select="@name"/>
    <xsl:text>
</xsl:text>

    <xsl:apply-templates select="wsdl:operation"/>
  </xsl:template>


  <!-- dump out the messages associated with each binding operation -->
  <xsl:template match="wsdl:binding/wsdl:operation">

    <xsl:text>  operation: </xsl:text>
    <xsl:value-of select="@name"/>
    <xsl:text>
</xsl:text>

    <xsl:variable name="porttype-key">
      <xsl:apply-templates select=".." mode="porttype-key"/>
    </xsl:variable>

    <xsl:variable name="opname">
      <xsl:value-of select="@name"/>
    </xsl:variable>

    <xsl:for-each select="wsdl:input">
      <xsl:text>    input: </xsl:text>
      <xsl:value-of select="@name"/>
      <xsl:text> -> message: </xsl:text>

      <xsl:for-each select="key('porttype',$porttype-key)/
                            wsdl:operation[@name=$opname]/
                            wsdl:input[@name=current()/@name]">
        <xsl:value-of select="@message"/>
      </xsl:for-each>
      <xsl:text>
</xsl:text>
    </xsl:for-each>

    <xsl:if test="not(wsdl:input)">
      <xsl:text>    (no inputs)
</xsl:text>
    </xsl:if>

  </xsl:template>

</xsl:stylesheet>

Running the above example against the test WSDL document produces:

Listing 16. Technique 5 example output
binding: binding
  operation: porttype-op1
    input: porttype-op1-input -> message: test:inputmsg1
  operation: porttype-op2
    input: porttype-op2-input -> message: test:inputmsg2
  operation: porttype-op3
    input: porttype-op3-input -> message: test:inputmsg3
  operation: porttype-op4
    (no inputs)

Technique 6: Correlate service ports with bindings

The final qname mapping is from the wsdl:service/wsdl:port back to the wsdl:binding. Again, named keys provide the solution.

Listing 17. Technique 6 XSL
<!-- index of wsdl:binding by namespace uri and wsdl:binding/@name -->

<xsl:key name="binding" match="wsdl:binding"
         use="concat(/wsdl:definitions/@targetNamespace,' ',@name)"/>

         
<!-- binding key value for wsdl:port -->

<xsl:template match="wsdl:port" mode="binding-key">
  <xsl:call-template name="namespace-uri-of-qname">
    <xsl:with-param name="qname" select="@binding"/>
  </xsl:call-template>
  
  <xsl:text> </xsl:text>

  <xsl:call-template name="local-name-of-qname">
    <xsl:with-param name="qname" select="@binding"/>
  </xsl:call-template>
</xsl:template>

This example puts together many of the techniques described here to generate a summary of the operations provided by each wsdl:servicein a WSDL document.

Listing 18. Technique 6 example
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:import href="wsdl-util.xsl"/>
  <xsl:strip-space elements="*"/>
  <xsl:output method="text"/>


  <!-- just process the services -->
  <xsl:template match="wsdl:definitions">
    <xsl:apply-templates select="wsdl:service"/>
  </xsl:template>


  <!-- dump out some information for each service -->
  <xsl:template match="wsdl:service">
    <xsl:text>service: </xsl:text>
    <xsl:value-of select="@name"/>
    <xsl:text>
</xsl:text>

    <xsl:apply-templates select="wsdl:port"/>
  </xsl:template>


  <!-- follow the linkages from the wsdl:port through the
       wsdl:binding, back to the wsdl:portType and its 
       wsdl:operations -->
  <xsl:template match="wsdl:port">

    <!-- key for looking up the wsdl:binding -->
    <xsl:variable name="binding-key">
      <xsl:apply-templates select="." mode="binding-key"/>
    </xsl:variable>

    <xsl:for-each select="key('binding',$binding-key)">
      <!-- key for looking up the wsdl:portType -->
      <xsl:variable name="porttype-key">
        <xsl:apply-templates select="." mode="porttype-key"/>
      </xsl:variable>

      <!-- process each operation in the wsdl:portType -->
      <xsl:for-each select="key('porttype',$porttype-key)/wsdl:operation">
        <xsl:apply-templates select="."/>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>


  <!-- generate a Java-like method signature for each operation -->
  <xsl:template match="wsdl:portType/wsdl:operation">

    <xsl:text>  </xsl:text>

    <!-- return type -->
    <xsl:for-each select="wsdl:output">

      <!-- get the correct value for the message name key -->
      <xsl:variable name="output-message-key">
        <xsl:apply-templates select="." mode="message-key"/>
      </xsl:variable>

      <!-- output the type of the first part of the message -->
      <xsl:for-each
        select="key('message',$output-message-key)/wsdl:part[1]">
        <xsl:value-of select="@type"/>
      </xsl:for-each>

    </xsl:for-each>

    <xsl:if test="not(wsdl:output)">
      <xsl:text>void</xsl:text>
    </xsl:if>


    <!-- method name -->
    <xsl:text> </xsl:text>
    <xsl:value-of select="@name"/>


    <!-- arguments -->
    <xsl:text>(</xsl:text>
    <xsl:for-each select="wsdl:input">

      <!-- get the correct value for the message name key -->
      <xsl:variable name="input-message-key">
        <xsl:apply-templates select="." mode="message-key"/>
      </xsl:variable>

      <!-- output the type of the type and name of each part of
           the message -->
      <xsl:for-each select="key('message',$input-message-key)/wsdl:part">
        <xsl:value-of select="@type"/>
        <xsl:text> </xsl:text>
        <xsl:value-of select="@name"/>
        <xsl:if test="not(last())">
          <xsl:text>, </xsl:text>
        </xsl:if>
      </xsl:for-each>

    </xsl:for-each>
    <xsl:text>)</xsl:text>


    <!-- exceptions -->
    <xsl:if test="wsdl:fault">
      <xsl:text> throws </xsl:text>
      <xsl:for-each select="wsdl:fault">

        <!-- get the correct value for the message name key -->
        <xsl:variable name="fault-message-key">
          <xsl:apply-templates select="." mode="message-key"/>
        </xsl:variable>

        <xsl:for-each select="key('message',$fault-message-key)">
          <xsl:value-of select="@name"/>
        </xsl:for-each>

        <xsl:if test="not(last())">
          <xsl:text>, </xsl:text>
        </xsl:if>

      </xsl:for-each>
    </xsl:if>

    <xsl:text>;
</xsl:text>

  </xsl:template>

</xsl:stylesheet>

Running the above example against the test WSDL document produces:

Listing 19. Technique 6 example output
service: service
  void porttype-op1(xsd:string inputmsg1-part);
  xsd:base64Binary porttype-op2(xsd:decimal inputmsg2-part) throws faultmsg;
  xsd:base64Binary porttype-op3(xsd:integer inputmsg3-part) throws faultmsg;
  xsd:base64Binary porttype-op4();

Technique 7: Importing additional definitions

This is more of a best practice than a technique per se, but it has proven extremely useful. When transforming WSDL documents into new WSDL documents, it is often tempting to insert additional data types and messages directly into the new WSDL document. If this new information is static (that is, does not depend on the input WSDL) consider using the WSDL import mechanism instead. Importing these definitions gives you the ability to change them later without re-transforming all the WSDL documents. Due to the nature of this technique, it is not included in the downloadable wsdl-util.xsl.

Listing 20. Technique 7 XSL
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:output method="xml" indent="yes"/>
  <xsl:preserve-space elements="*"/>


  <!-- add a wsdl:import for mystuff.wsdl -->

  <xsl:template match="wsdl:definitions">
    <xsl:copy>
      <xsl:copy-of select="@*"/>

      <wsdl:import namespace="http://tempuri.org/mystuff"
                   location="mystuff.wsdl"/>

      <xsl:copy-of select="node()| comment()|text()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Here are the relevant portions of test.wsdl after being updated by the XSL shown above:

Listing 21. Technique 7 example output
<?xml version="1.0"?>
<wsdl:definitions name="test"
  targetNamespace="http://tempuri.org/test/"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:test="http://tempuri.org/test/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">

  <wsdl:import namespace="http://tempuri.org/mystuff" location="mystuff.wsdl"/>

  <wsdl:message name="inputmsg1">
    <wsdl:part name="inputmsg1-part" type="xsd:string"/>
  </wsdl:message>

  ...

Technique 8: Append a suffix to an attribute value

When transforming WSDL documents, it is often necessary to derive new and/or additional definitions from existing definitions. To help preserve the origin of these new definitions, it is often useful to assign them names similar to the source elements from which they are derived. Due to the nature of this technique, it is not included in the downloadable wsdl-util.xsl.

The following XSL illustrates one way to accomplish this by appending a suffix to the existing name.

Listing 22. Technique 8 XSL
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:output method="xml" indent="yes"/>
  <xsl:preserve-space elements="*"/>


  <!-- Except as overridden by more specific templates below, do a
       deep copy of everything. -->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>


  <!-- append a suffix to wsdl:portType/@names -->

  <xsl:template match="wsdl:portType/@name">
    <xsl:attribute name="{name(.)}">
      <xsl:value-of select="."/>
      <xsl:text>-mysuffix</xsl:text>
    </xsl:attribute>
  </xsl:template>
</xsl:stylesheet>

Here are the relevant portions of test.wsdl after being updated by the XSL shown above:

Listing 23. Technique 8 example output
  ...
  <wsdl:message name="faultmsg"/>
    
    
  <wsdl:portType name="porttype-mysuffix">
    <!-- one-way operation -->
    <wsdl:operation name="porttype-op1">
       <wsdl:input message="test:inputmsg1" name="porttype-op1-input"/>
    </wsdl:operation>
    ...

Themes

In conclusion, here are some higher-level themes from the techniques shown above for processing WSDL documents with XSL. Hopefully they will save you some time and mental anguish the next time you need to hack a WSDL document with XSL:

  1. Always be prepared for null namespaces on qualified names. While this almost always adds complexity to the XSL transforms, wsdl:definitions/@targetNamespace is optional.
  2. Change namespace prefixes to URIs as soon as possible. Namespace prefixes are basically meaningless to anyone except the author of the original document and relying on a constant one-to-one mapping of namespace prefixes to URIs is unwise at best.
  3. When trying to correlate constructs in a WSDL document, keys are almost always a better choice than XPath expressions. This is largely a result of the previous two items, but in almost every case using keys instead of XPath expressions results in simpler and shorter XSL transforms.
  4. Pay very close attention to current() when evaluating namespaces. The XSL constructs for mapping from namespace prefixes to URIs work best for current(). Trying to evaluate namespace prefixes for a node other than current() is error-prone.
  5. Import rather than augment. When adding new types and messages to a WSDL document, it is tempting to simply include the definitions in the output WSDL document. Unless these changes are cast in concrete (for example, part of a published standard), it is almost always a better idea to place the new definitions in a separate WSDL document and add an import element to the generated document. Doing so makes future changes significantly less painful.
  6. Be prepared for multi-part WSDL documents. While this might not be a common usage pattern with current tooling, the WSDL specification specifically describes how to split a WSDL document across multiple files. Don't assume that simple XPath expressions will correctly navigate among sections of the WSDL document that reside in different input files. Multi-part WSDL documents can also have more subtle effects. For example, it is not always possible to determine if a message is used as an input or output since the definitions of the message and the operations that use it can be in separate files.

XSL transforms allow you to make complex changes to WSDL documents in an automated fashion. The approaches and XSL snippets contained in this article can give you a jump start processing WSDL documents and help you avoid the most common pitfalls.


Downloadable resources


Related topic


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=SOA and web services, XML
ArticleID=103949
ArticleTitle=Processing WSDL documents with XSLT
publish-date=02142006