I’ve been struggling with the concepts of parsing FoxPro objects into XML for use in Web Services as part of wwSOAP for quite a while. My wwXML utility makes it relatively painless to generate XML from an object, but this XML is limited to lower case characters for entity names because VFP does not support proper case objects.

It works but as you can see all the property names lower case, which is a problem if you need to send this object to a server that uses proper names. Which is likely if you’re calling a .NET or Java Web Service.

A number of people last week at DevCon were asking about how to properly convert objects and my answer has been to just bite the bullet and hand create the XML. You can do this fairly easily especially if you use wwXML’s AddElement method to do the proper type conversions for you. But this is still a major hassle.

Sooooo… after some more thought about this I decided that there is another way to do this: You can use the WSDL type definition to parse an object and instead of using the property names from VFP to generate the element names we can use the property names defined in the WSDL file. This is not too terribly complicated, but it does get a bit tricky for array and collection handling. Luckily when calling .NET services at least collections must be typed properly and persist very much the same way as arrays so the structural layout for both of these is similar.

By doing this you are limited to the structure that is in the WSDL file so you have to create an object that maps property names to what’s available in the Web Service. In the end I created a parser in the free wwSOAP class that looks something like this:

This code uses wwXML and AddElement quite extensively but it turned out to be a relatively short piece of code. If it wasn’t for the handling of arrays this code would have been pretty clean and short. The difficult part here is that we have to make sure that we can figure out the name of the child elements in arrays.

This code assumes that collections or arrays contain objects, not individual values. Further this code doesn’t deal well with recursive references. If the object in the tree has child objects that point back at another object higher up it can easily get hung up.

However, you can now create objects on the fly that match fairly complex objects. For example, here the sample I was doing at DevCon (minus the automatic XML creation):

loAuthor = GetAuthorProxyObject()

loAuthor.au_lname = "Strahl"

loAuthor.au_fname = "Rick"

loAuthor.Phone = "808 123-1211"

loAuthor.Address = "32 Kaiea Place"

loAuthor.City = "Paia"

loAuthor.State = "HI"

loAUthor.Zip = "96779"

loAuthor.Contract = 0

loAuthor.Invoices[1] = loInv

loAuthor.Invoices[2] = loInv2

FUNCTION GetAuthorProxyObject()

*** Manunally parse the result back into an object

loAuthor = CREATEOBJECT("Relation")

loAuthor.AddProperty("Au_id","")

loAuthor.AddProperty("Au_lname","")

loAuthor.AddProperty("Au_fname","")

loAuthor.AddProperty("Phone","")

loAuthor.AddProperty("Address","")

loAuthor.AddProperty("City","")

loAuthor.AddProperty("State","")

loAuthor.AddProperty("Zip","")

loAuthor.AddProperty("Contract",.f.)

loAuthor.AddProperty("PhoneNumbers",CREATEOBJECT("RELATION"))

loAuthor.PhoneNumbers.AddProperty("HomePhone","")

loAuthor.PhoneNumbers.AddProperty("WorkPhone","")

loAuthor.PhoneNumbers.AddProperty("Fax","")

loAuthor.PhoneNumbers.AddProperty("ServiceStarted",DATETIME())

*** Code to add an array for invoices

loAuthor.AddProperty("Invoices")

*DIMENSION loAuthor.Invoices[2]

*LOCAL loInvoices as Collection

loAuthor.Invoices = CREATEOBJECT("Collection")

loInv = CREATEOBJECT("Empty")

ADDPROPERTY(loInv,"InvoiceNumber","9")

ADDPROPERTY(loInv,"InvoiceDate",{05/10/2004 :})

ADDPROPERTY(loInv,"InvoiceTotal",102.00)

loAuthor.Invoices.Add(loInv)

*loAuthor.Invoices[1] = loInv

loInv = CREATEOBJECT("Empty")

ADDPROPERTY(loInv,"InvoiceNumber","10")

ADDPROPERTY(loInv,"InvoiceDate",{01/10/2004 :})

ADDPROPERTY(loInv,"InvoiceTotal",199.00)

*loAuthor.Invoices[2] = loInv

loAuthor.Invoices.Add(loInv)

RETURN loAuthor

The whole thing then yields:

<AuthorEntity>

<PhoneNumbers>

<HomePhone/>

<WorkPhone/>

<Fax/>

<ServiceStarted>2004-10-07T00:35:08</ServiceStarted>

</PhoneNumbers>

<Au_id/>

<Au_lname>Strahl</Au_lname>

<Au_fname>Rick</Au_fname>

<Phone>80_ 123-1211</Phone>

<Address>32 Kaiea Place</Address>

<City>Paia</City>

<State>HI</State>

<Zip>96779</Zip>

<Contract>0</Contract>

<Invoices>

<Invoice>

<InvoiceNumber>9</InvoiceNumber>

<InvoiceTotal>102</InvoiceTotal>

<InvoiceDate>2004-05-10T00:00:00</InvoiceDate>

</Invoice>

<Invoice>

<InvoiceNumber>10</InvoiceNumber>

<InvoiceTotal>199.90</InvoiceTotal>

<InvoiceDate>2004-01-10T00:00:00</InvoiceDate>

</Invoice>

<Invoices>

</AuthorEntity>

You can then take this entire XML string and turn it into a NodeList and send it back up to the server using the SOAP toolkit or wwSOAP:

So now, wwSOAP and wwXML provide the ability to both generate objects to XML and parse them back into objects (although this requires a ‘blue print object’).

On the inbound side I’ve also enhanced ParseObject which uses the WSDL file to dynamically construct an object on the fly and assign the properties into it. This routine goes the other way by looking at the WSDL file and adding properties to an empty object, then reading the value from the XML document into it. This has worked for the DevCon sessions and samples, but I’ve also added the ability to parse arrays (and by that route – collections).

In fact the code with wwSOAP to parse Web Service response from above looks like this:

oSOAP = CREATEOBJECT("wwSOAP")

oSOAP.nHttpConnectTimeout = 5

oSOAP.lParseReturnedObjects = .T.

*** Retrieve authors - returns DataSet returned as NodeList

oSOAP.AddParameter("Id","486-29-1786")

loAuthor = oSOAP.CallWSDLMethod("GetAuthorEntity",lcWSDL)

? loauthor.au_lname

? loAuthor.PhoneNumbers.HomePhone

? loAuthor.PhoneNUmbers.ServiceStarted

*** Array properties? loAuthor.Invoices[1].InvoiceNumber

? loAuthor.Invoices[2].InvoiceTotal

This also works with MSSOAP. You can call the ParseObject method directly, but it requires picking up the WSDL file again so there’s some extra overhead there:

o = CREATEOBJECT("MSSOAP.SoapClient30")

o.MSSoapInit(lcWSDL)

loNL = o.GetAuthorEntity("486-29-1786")

*** Retrieve the Root Node

loRoot = loNL.Item(0).ParentNode

loSOAP = CREATEOBJECT("wwSOAP")

loSOAP.ParseServiceWSDL(lcWSDL,.t.)

loAuthor = loSOAP.ParseObject(loRoot,"AuthorEntity")

This is pretty cool, even though this code is probably not 100% foolproof. For one thing you can run into problems if you end up with property names that VFP will not allow or get confused by. This code could be made more reliable by making it VFP 8 and later using the EMPTY object and the ADDPROPERTY() function instead of the AddProperty() method of the RELATION object.

I haven’t posted the update to wwSOAP yet, but look for it over the next few days. I’ll also update the Web Service .NET Interop article with some of these new functions which will reduce the code a bit and provide a solution to one of the missing pieces namely passing object up to the server.

Cool. I'll have to look this up again when I get back to another project. I hated having to change my variable names in .NET to all lowercase just because VFP couldn't handle/pass up the mixed-case version.

Yeah, no kidding. unfortunately there are still a few open issues, like how to deal with embedded entity objects such as datasets or XML nodes. The WSDL parser will choke on those. There's no standard way that these things are in the WSDL, so it's really hard to tell what they are at parse time.