WebReference.com - Chapter 30 of Curl Programming Bible, from John Wiley & Sons (7/8) | WebReference

WebReference.com - Chapter 30 of Curl Programming Bible, from John Wiley & Sons (7/8)

To page 1To page 2To page 3To page 4To page 5To page 6current pageTo page 8
[previous] [next]

Curl Programming Bible, chapter 30

Creating a custom mapping for a struct

The SOAP specification defines a struct as a compound value in which the accessor name is the only distinction among member values, and no accessor has the same name as any other. More simply, a struct is an aggregate of values of arbitrary types, each value identified by a unique name.

Consider a struct that contains two values, both of type string. One value of the struct is identified by IP and the other by Hostname. The SOAP encoding rules represent a struct as a sequence of elements. The names of the elements are names associated with the values. So, the SOAP encoding for struct would look like this:

<IP>167.216.248.234</IP>
<Hostname>www.curl.com</Hostname>

NOTE: A struct may have an arbitrary number of fields and each field may have an arbitrary type. While our example has fields of the same type, this is not the general case for structs.

Suppose you want to call a Web service that performs a DNS lookup. The SOAP operation takes a single input argument and single output argument. Its input argument is a string containing either an IP address or hostname and its output argument is a struct containing both an IP address and a hostname.

In order to call this DNS lookup Web service, write a general-purpose class named Soap-1-1-StructureDescriptor to handle the mapping of structs. The class handles the marshaling and unmarshaling between the struct and the corresponding Curl type. The constructor for the class takes the XML name for the struct, the corresponding Curl type, and a Soap-1-1-StandardArgumentDescriptor for each of the fields of the struct. For the struct just shown, the call to the constructor is

{let structure-descriptor:Soap-1-1-StructureDescriptor =
    {new Soap-1-1-StructureDescriptor,
        {new XMLName, "", "InternetAddress"},
        InternetAddress,
        {new Soap-1-1-StandardArgumentDescriptor,
            {new XMLName, "", "IP"},
            {new XMLName, xsd, "string"},
            String
        },
        {new Soap-1-1-StandardArgumentDescriptor,
            {new XMLName, "", "Hostname"},
            {new XMLName, xsd, "string"},
            String
        }
    }
}

NOTE: We are using the accessor names of the struct fields in the Soap-1-1-StandardArgumentDescriptor passed to the Soap-1-1-StructureDescriptor constructor. This works because the encoding rules for arguments are similar to the encoding rules for structs. In either case, the accessor or argument name is used as the tag name of the element that encloses the field or argument value.

Below are the field declarations and the constructor for the Soap-1-1-StructureDescriptor class. The constructor is called with the name for the XML type of the struct, the corresponding Curl type for the struct, and a sequence of Soap-1-1-StandardArgumentDescriptors, one for each field in the struct.

{define-class public Soap-1-1-StructureDescriptor
  || mapping between curl-type and xml-type
  field public-get private-set curl-type:Type
  field public-get private-set xml-type:XMLName
  || element descriptors
  field public-get private-set element-descriptors: {Array-of
                                                      Soap-1-1-StandardArgumentDescriptor}
  field public-get private-set marshal:XMLMarshaler
  field public-get private-set unmarshal:XMLUnmarshaler
  
  {constructor public {default 
                          xml-type:XMLName,
                          curl-type:Type,
                          ...:Soap-1-1-StandardArgumentDescriptor
                      }
    set self.curl-type=curl-type
    set self.xml-type=xml-type
    
    set self.element-descriptors={new {Array-of Soap-1-1-StandardArgumentDescriptor}}
    {for element-descriptor:Soap-1-1-StandardArgumentDescriptor in ... do
        {self.element-descriptors.append element-descriptor}
    }

Soap-1-1-StructureDescriptor uses the Curl supplied class named Arguments. Arguments is the class used to represent an argument list in Curl. Curl allows both positional and keyword arguments so the Arguments class can represent both kinds of arguments. Soap-1-1-StructureDescriptor only uses keyword arguments in Arguments values.

The Soap-1-1-StrucutreDescriptor class makes some assumptions about the Curl type that maps to the struct. It assumes it has a method that converts the value of the Curl type to the Arguments type. The value of the Arguments type contains keyword arguments of the form name=value, where name corresponds to an accessor name and value is its corresponding value. The Soap-1-1-StructureDescriptor class also assumes the Curl type has a constructor that accepts keyword arguments of the form name=value, where name is an accessor name and value is the corresponding value. Here's the Curl class InternetAddress that corresponds to the struct shown above and adheres to the assumptions of the Soap-1-1-StructureDescriptor class:

{define-class public InternetAddress {inherits SoapStruct}
  field public ip:String
  field public hostname:String
  {constructor public {default IP:String="", Hostname:String=""}
    set self.ip=IP
    set self.hostname=Hostname
  }
  {method public {to-Arguments}:Arguments
    {return
        {Arguments
            IP=self.ip,
            Hostname=self.hostname
        }
    }
  }
}
The SoapStruct class is an abstract class containing the to-Arguments method. The Soap-1-1-StructureDescriptor class assumes that the Curl type that maps the struct inherits the SoapStruct class. Here is the SoapStruct class:
{define-class public abstract SoapStruct
  {method public abstract {to-Arguments}:Arguments}
}

The interesting part of Soap-1-1-StructureDescriptor is the marshaling and unmarshaling procedure it provides to convert between the struct and the corresponding Curl type. Consider the unmarshaling procedure:

       set self.unmarshal =
        || all unmarshler must be of this proc-type
        {proc {map:XMLCurlMappingRegistry,
               encoding-style:String, 
               in:XMLInputStream 
              }:any
            || we'll collect the XML struct into Arguments
            || in the form of <field-name>=<value>
            let struct:Arguments = {Arguments}
            let done?:bool = false
            || read the fields of the XML struct
            || what we're expecting is a series a values
            || embedded in element where the tag name of 
            || the element is the field name in the struct
            {until tag=read-struct-element, done? do
                let xml-token:XMLToken = {in.read-one}
                {type-switch xml-token
                 case end:XMLEndElement do
                    || if it's the end of an element, we're done
                    {in.unread-one end}
                    set done? = true
                 case start:XMLStartElement do
                    || it's the start of an element -- another field of the struct
                    {for element-descriptor:Soap-1-1-StandardArgumentDescriptor in self.element-descriptors do
                        || match the tag name of the element with a name in the descriptor
                        {if start.element == element-descriptor.name then
                            || skip over attributes 
                            {while true do
                                let xml-token:XMLToken={in.read-one}
                                {type-switch xml-token
                                 case attribute:XMLAttribute do
                                    {continue}
                                 else
                                    {in.unread-one xml-token}
                                    {break}
                                }
                            }
                            || unmarshal the value using the information in descriptor
                            let value:any =
                                {map.unmarshal 
                                    encoding-style, 
                                    element-descriptor.curl-type, 
                                    element-descriptor.xml-type, 
                                    in
                                }
                            || matching end element
                            let end:XMLEndElement = {in.read-one} asa XMLEndElement
                            || append <field-name>=<value>
                            {struct.append keyword = element-descriptor.name.local-name, value}
                            {continue tag=read-struct-element}
                        }
                    }
                    || element name doesn't match name in descriptor
                    {error "unrecognized element name:" & start.element}
                 else
                    {error "unexpected XMLToken: " & xml-token}
                }
            }
            let result:any =
                || Curl type is expected to be substype of SoapStruct
                {if {self.curl-type.subtype-of? SoapStruct} then
                    let t:ClassType = self.curl-type asa ClassType
                    || call class constructor with <field-name>=<value> list
                    || compiled from XML str
                    {t {splice struct}}
                 else
                    {error "expect subclass of SoapStruct"}
                }
            {return result}
         t}
        }

The Soap-1-1-StructureDescriptor provides a method to register the marshaling and unmarshaling procedures that map between the struct and the corresponding Curl type. The method takes an XMLCurlMappingRegistry as an argument. Here is the call to the DNS lookup Web service:

|| create a registry
{let my-registry:XMLCurlMappingRegistry = {new XMLCurlMappingRegistry}}
|| register the struct mapping
{structure-descriptor.register my-registry}
|| make the  SOAP call
{let anys:{Array-of any}={DNS-lookup-operation.call 
                             "www.curl.com",
                             registry=my-registry
                         }
}
|| display the results 
Hostname: {value (anys[0] asa InternetAddress).hostname}
{br}
IP address: {value (anys[0] asa InternetAddress).ip}

The full applet (structure-descriptor.curl) is on the accompanying Web site.


To page 1To page 2To page 3To page 4To page 5To page 6current pageTo page 8
[previous] [next]

Created: August 14, 2002
Revised: August 14, 2002

URL: http://webreference.com/programming/curlbible/chap30/7.html