Updated 2014-01-25 16:07:41 by RLE

snichols I'm working on a Tcl client to integrate to a web service. I have an example of how the SOAP envelope should look. It is using two complex data types (WebUser and Authentication). I'd prefer to use Tcl SOAP then have to code the XML using tDOM and HTTP, which I will do if necessary. Here's my Tcl code that generates a SOAP request envelope that's close to the working SOAP envelope, but not good enough for the web service (there are some extra XML attributes, etc that are needed):
 package require SOAP
 package require rpcvar
 namespace import -force rpcvar::typedef

 typedef {
        Username DATA
        Password DATA
        Nonce DATA
        Created DATA
        RemoteUser DATA
        RemoteClient DATA
        RemoteAddress DATA
        EnvironmentalData DATA
        SessionID DATA
 } authentication

 typedef {
   Authentication  authentication
   ApplicationType DATA
   RequestID DATA
   WebUserID DATA
   WebUserName DATA
   PlanID DATA
   EmployerID DATA
   MemberID DATA
   EntityType MEMBER
   EntityReferenceNumber DATA
   Password DATA
   EncryptionMethod PLAINTEXT
   InputMethodEditor TELEPHONE
 } webUser

 SOAP::create Test -proxy "http://localhost/superSOAP/webcontrol.asmx" \
                        -action "http://www.atune.biz/web/services/WC/2006-01/isWebUserAuthenticated" \
                        -uri "http://www.atune.biz/web/services/WC/2006-01" \
                        -params {WebUser webUser}

When the soap method, isWebUserAuthenticated, is called it generates this as the SOAP request :
 <?xml version="1.0"?>
 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-   ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance">
        <SOAP-ENV:Body>
                <ns:isWebUserAuthenticated xmlns:ns="http://www.atune.biz/web/services/WC/2006-01">
                        <WebUser>
                                <Authentication>
                                        <Username>IVRUSER</Username>
                                        <Password>newPassword</Password>
                                        <Nonce></Nonce>
                                        <Created></Created>
                                        <RemoteUser>TESTER</RemoteUser>
                                        <RemoteClient>SomeCompany</RemoteClient>
                                        <RemoteAddress>127.0.0.1</RemoteAddress>
                                        <EnvironmentalData></EnvironmentalData>
                                        <SessionID></SessionID>
                                </Authentication>
                                <ApplicationType>MOL</ApplicationType>
                                <RequestID></RequestID>
                                <WebUserID></WebUserID>
                                <WebUserName>11111</WebUserName>
                                <PlanID>RS</PlanID>
                                <EmployerID>-1</EmployerID>
                                <MemberID>-1</MemberID>
                                <EntityType>MEMBER</EntityType>
                                <EntityReferenceNumber>0</EntityReferenceNumber>
                                <Password>5555</Password>
                                <EncryptionMethod>PLAINTEXT</EncryptionMethod>
                                <InputMethodEditor>TELEPHONE</InputMethodEditor>
                        </WebUser>
                </ns:isWebUserAuthenticated>
        </SOAP-ENV:Body>
 </SOAP-ENV:Envelope>

Its very close, but I need the SOAP above envelope to look like this working version (both are very close):
        <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <soap:Body>
      <isWebUserAuthenticated xmlns="http://www.atune.biz/web/services/WC/2006-01">

      <WebUser>
        <Authentication Enabled="1">
          <Username>IVRUSER</Username>
          <Password Type="PLAINTEXT" Token="*NONE*">newPassword</Password>
          <Nonce></Nonce>
          <Created>20060309T080101</Created>
          <RemoteUser>TESTER</RemoteUser>
          <RemoteClient>SomeCompany</RemoteClient>
          <RemoteAddress>127.0.0.1</RemoteAddress>
          <EnvironmentData></EnvironmentData>
          <SessionID></SessionID>
        </Authentication>
        <ApplicationType>MOL</ApplicationType>
        <RequestID></RequestID>
        <WebUserID></WebUserID>
        <WebUserName>157014233</WebUserName>
        <PlanID>RS</PlanID>
        <EmployerID>-1</EmployerID>
        <MemberID>-1</MemberID>
        <EntityType>MEMBER</EntityType>
        <EntityReferenceNumber>0</EntityReferenceNumber>
        <Password>5555</Password>
        <EncryptionMethod>PLAINTEXT</EncryptionMethod>
       <InputMethodEditor>TELEPHONE</InputMethodEditor>
      </WebUser>

          </isWebUserAuthenticated>
   </soap:Body>
 </soap:Envelope>

Any ideas what I need to change in my TclSOAP command calls to make the SOAP envelope look like the latter XML above? Its currently generating the first XML. I notice a couple of things different: The latter has an Enabled="1" attribute and the password tag has some more attributes too, and its not using the /ns: namespace like the first. The latter envelop works and returns a result the first request returns errors. The other thing thats differnt is the second version is using simpler looking tags like soap:Evelope and soap:Body. So, how do I add the XML attributes needed by the webservice, etc?

Thanks in advance. snichols.

lexfiend: It doesn't directly answer your question, but since you already know exactly what the SOAP request must look like, you might want to consider the XML-template method I devised and documented in WSDL. I've found it to be more cost-effective (read: less time + hair-pulling) than trying to tweak my code to coax TclSOAP to satisfy different Web services (and possibly having to re-tweak it for newer releases of TclSOAP).

snichols Thanks. I saw your example in the WSDL page in the Wiki. It is a very quick fix, but I don't know if its the best way since you are not using an XML Tcl package to do your replacing. There's a potential for XML special characters in the values you are replacing in your XML template. I don't know how likely this is, and if it does fail what's the outcome: some user somewhere that has to use a different method for support. I may end up using what you did. Thanks again.

snichols Mar 14th 2006 Well, I was able to get the SOAP envelop built in a way the web service understood. Below is the complete working Tcl code. Basically, all I had to do was change the name of the root xmlns attributes from SOAP-ENV to soap and remove the body xmlns ns attribue and it worked. It even works with out setting the body attributes. I'm happy with this outcome because I was hoping to reuse the Tcl SOAP API as much as possible. Would someone mind providing an example of how to edit a SOAP body element attribute? I wasn't succesful in edit the attributes using XPath I had to use next child, next sibling to edit them. Is this normal?
 package require SOAP
 package require rpcvar
 package require tdom
 namespace import -force rpcvar::typedef

 # Create two complex data types: Authentication and Web User
 # These are required by the web service.
 typedef {
        Username DATA
        Password DATA
        Nonce DATA
        Created DATA
        RemoteUser DATA
        RemoteClient DATA
        RemoteAddress DATA
        EnvironmentData DATA
        SessionID DATA
 } authentication

 typedef {
   Authentication  authentication
   ApplicationType DATA
   RequestID DATA
   WebUserID DATA
   WebUserName DATA
   PlanID DATA
   EmployerID DATA
   MemberID DATA
   EntityType MEMBER
   EntityReferenceNumber DATA
   Password DATA
   EncryptionMethod PLAINTEXT
   InputMethodEditor TELEPHONE
 } webUser

 # Custom Authentication XML Envelope Procedure.
 # This was needed for the soap namespaces and removing the ns namespace
 proc ::SOAP::AuthenticationWrapper {procVarName args} {
    upvar $procVarName procvar

    set procName [lindex [split $procVarName {_}] end]
    set params  $procvar(params)
    set name    $procvar(name)
    set uri     $procvar(uri)
    set soapenv $procvar(version)
    set soapenc $procvar(encoding)

    # Check for options (ie: -header) give up on the fist non-matching arg.
    array set opts {-headers {} -attributes {}}
    while {[string match -* [lindex $args 0]]} {
        switch -glob -- [lindex $args 0] {
            -header* {
                set opts(-headers) [concat $opts(-headers) [lindex $args 1]]
                set args [lreplace $args 0 0]
            }
            -attr* {
                set opts(-attributes) [concat $opts(-attributes) [lindex $args 1]]
                set args [lreplace $args 0 0]
            }
            -- {
                set args [lreplace $args 0 0]
                break
            }
            default {
                # stop option processing at the first invalid option.
                break
            }
        }
        set args [lreplace $args 0 0]
    }

    # check for variable number of params and set the num required.
    if {[lindex $params end] == "args"} {
        set n_params [expr {( [llength $params] - 1 ) / 2}]
    } else {
        set n_params [expr {[llength $params] / 2}]
    }

    # check we have the correct number of parameters supplied.
    if {[llength $args] < $n_params} {
        set msg "wrong # args: should be \"$procName"
        foreach { id type } $params {
            append msg " " $id
        }
        append msg "\""
        return -code error $msg
    }

    set doc [dom::DOMImplementation create]
    set envx [dom::document createElement $doc "soap:Envelope"]

    dom::element setAttribute $envx "xmlns:soap" $soapenv
    dom::element setAttribute $envx "xmlns:SOAP-ENC" $soapenc
    dom::element setAttribute $envx "soap:encodingStyle" $soapenc

    # The set of namespaces depends upon the SOAP encoding as specified by
    # the encoding option and the user specified set of relevant schemas.
    foreach {nsname url} [concat \
                              [rpcvar::default_schemas $soapenc] \
                              $procvar(schemas)] {
        if {! [string match "xmlns:*" $nsname]} {
            set nsname "xmlns:$nsname"
        }
        dom::element setAttribute $envx $nsname $url
    }

    # Insert the Header elements (if any)
    if {$opts(-headers) != {}} {
        set headelt [dom::document createElement $envx "soap:Header"]
        foreach {hname hvalue} $opts(-headers) {
            set hnode [dom::document createElement $headelt $hname]
            insert_value $hnode $hvalue
        }
    }

    # Insert the body element and atributes.
    set bod [dom::document createElement $envx "soap:Body"]
    if {$uri == ""} {
        # don't use a namespace prefix if we don't have a namespace.
        set cmd [dom::document createElement $bod "$name" ]
    } else {
        set cmd [dom::document createElement $bod "$name" ]
        dom::element setAttribute $cmd "xmlns" $uri
    }

    # Insert any method attributes
    if {$opts(-attributes) != {}} {
        foreach {atname atvalue} $opts(-attributes) {
            dom::element setAttribute $cmd $atname $atvalue
        }
    }

    # insert the parameters.
    set param_no 0
    foreach {key type} $params {
        set val [lindex $args $param_no]
        set d_param [dom::document createElement $cmd $key]
        insert_value $d_param [rpcvar $type $val]
        incr param_no
    }

    # We have to strip out the DOCTYPE element though. It would be better to
    # remove the DOM node for this, but that didn't work.
    set prereq [dom::DOMImplementation serialize $doc]
    set req {}
    dom::DOMImplementation destroy $doc              ;# clean up
    regsub "<!DOCTYPE\[^>\]*>\r?\n?" $prereq {} req  ;# hack

    set req [encoding convertto utf-8 $req]          ;# make it UTF-8

        return $req                                      ;# return the XML data
 }

 SOAP::create isWebUserAuthenticated -proxy "http://localhost/superSOAP/webcontrol.asmx" \
                        -action "http://www.atune.biz/web/services/WC/2006-01/isWebUserAuthenticated" \
                        -uri "http://www.atune.biz/web/services/WC/2006-01" \
                        -params {WebUser webUser}
                        
 SOAP::configure isWebUserAuthenticated -wrapProc ::SOAP::AuthenticationWrapper