XML is becoming the messaging standard of choice, and one of the key issues in this architecture is the conversion and transfer of data between client and server sides.

In this article, Rick looks at a tool that easily converts Visual FoxPro tables and objects to and from XML, and demonstrates the concepts of XML messaging in a live e-Commerce application.

In the last issue, I gave an overview of how XML fits into a distributed messaging architecture. I described the pros and cons of using XML on an abstract level. While you may have heard those issues before, it helps to put them into perspective in an application development environment. Often, when you read the trade press the real value of a new technology like XML gets lost behind the noise and grandstanding that vendors use to promote their own technologies.

In this article, I want to show a few practical examples of how you can take advantage of XML with minimal effort in your own applications today. In the process, I introduce a free tool for Visual FoxPro that you can use to create XML from your data and objects, and pass that XML data between client and server applications over the Internet - with literally just a few lines of code.

Building tools to work with XML

The true power of XML really doesn't shine until you put it to work. To take advantage of XML, you can handcraft XML from a database table or data contained in objects, simply by building XML strings on the fly via code or by using an XMLDOM parser to generate a full XML document object.

If you're running in a COM-based environment, the parser of choice is the Microsoft XMLDOM parser, which ships with Internet Explorer 5.0 and later. The XMLDOM parser is XML 1.0 compliant and relatively easy to work with. XML parsers are meant to have a standard feature set, so once you get familiar with a parser you should be able to use it in different languages and environments. The XML parser you use inside VFP has the same syntax as the one you use in Internet Explorer (it's the same object after all). Within a Java application, you might use the same syntax in an XMLDOM implementation from a non-Microsoft source (such as IBM, Oracle or Sun). As all these parsers are XML 1.0 compliant the object syntax for all of them is, for the most part (except for vendor specific extensions), the same.

I'm not going to discuss in detail how the parser syntax works. If you haven't done so already, I suggest you pick up a text on XML (XML in Action by William Pardi, Microsoft Press, is a good one). If you're interested in how to parse XML via Visual FoxPro code, you can look at the source code accompanying this article in the wwXML class.

Parsing XML with the parser is not terribly tricky, but it's definitely tedious. Even worse, it's a repetitive process. You're going to parse data to and from data tables and objects, over and over again. To help you with this process in Visual FoxPro, I've provided a free class called wwXML. wwXML handles the following:

  • Converting VFP cursors to XML
  • Converting any ODBC/OleDB datasource resultset to XML, including stored procedures and multiple result sets.
  • Converting cursors into ADO-compatible XML that can be loaded directly by ADO recordsets.
  • Converting XML to cursors
  • Converting objects to XML (including subobjects)
  • Converting XML to objects (including subobjects)
  • Downloading XML from, and POSTing XML to a Web server

The wwXML class provides simple, single-call methods to perform these tasks, as well as low-level methods. The low-level methods can work on partial XML documents or XML fragments, to allow accessing XML that doesn't match wwXML's specific XML document structures.

Figure 2 - Item object rendered as HTML using standard templates with ASP syntax
Figure 2 - Item object rendered as HTML using standard templates with ASP syntax

For example, CursorToXML exports a cursor to a fully self-contained XML document with XML header and root node, but CreateCursorXML (a ‘low level’ method) creates only the inner XML fragment representing the cursor. The result of the latter can be strung together with additional XML fragments, or used as a subgroup in a hierarchical layout to build complex XML documents. Likewise, the parsing routines can be pointed at particular nodes in an XMLDOM document, to start parsing from a certain node rather than dealing with the entire XML document.

Here are a couple of simple examples that demonstrate how wwXML works. The first is a Web server-side piece of code that generates XML from a SQL SELECT statement, and then sends this XML back to a client over the Web. I'm using West Wind Web Connection in this scenario, but you can easily use a VFP component with ASP or FoxISAPI, with minor adjustments. Figure 1 shows what the result looks like viewed in raw form in Internet Explorer 5 (other browsers may not display the XML as XML text - you may have to View Source to see the XML).

Figure 1 - XML content generated on the server with wwXML. Note that the XML contains attributes that describe the structure of the data, which is defined in the DTD of the document.
Figure 1 - XML content generated on the server with wwXML. Note that the XML contains attributes that describe the structure of the data, which is defined in the DTD of the document.
FUNCTION XMLCursor

    SET CLASSLIB TO wwXML ADDITIVE
    SET PROCEDURE TO wwUtils ADDITIVE
    
    lcCompany = Request.QueryString("Company")
    
    oXML = CreateObject("wwXML")
    oXML.cDocRootName = "WebConnection"
    oXML.lCreateDataStructure  = .T.    && Add a DTD
    
    *** Run a query to render
    SELECT * FROM TT_CUST WHERE Company = lcCompany INTO CURSOR TQuery
    
    *** Generate the XML to a string
    lcXML = oXML.CursorToXML("customers","customer")
    IF oXML.lError
        THIS.ErrorMsg("XML Conversion Error",oXML.cErrorMsg)
        RETURN
    ENDIF
    
    *** Must create an XML header for browser to know to treat this document as XML
    Response.ContentType = "text/xml"
    Response.Write(lcXML)

To access this document, I can type the following URL into the browser (this is a live link, so you can try it yourself):

http://www.west-wind.com/wconnect/xmlcursor.wwd?Company=A

I can provide a single parameter to my query by adding a Company= key on the query string to limit the companies that are displayed as a query parameter in this case all that start with an A which is picked up on the server using the Request.QueryString(“Company”) code.

When XML output is generated, note that you should set the content type of the document to “text/xml” as shown in figure 1. This triggers Internet Explorer 5's default XSL stylesheet that lets you see the XML as a dynamic document, in which you can expand/collapse the various hierarchical levels of the XML document. IE5 is currently the only browser that supports native viewing of XML content.

The generated XML document contains structure information about the data, in the form of XML attributes, because I set the lCreateDataStructure property. When that property is set to .T., wwXML automatically generates a DTD (Document Type Definition) for the table that is exported. You can view the DTD by clicking View Source in the browser. At the top of the XML document you see:

<?xml version="1.0"?>
<!DOCTYPE WebConnection [
    <!ELEMENT WebConnection (customers)>
    <!ELEMENT customers (customer)*>
    <!ELEMENT customer (custno,company,careof,address,shipaddr,email,phone,billrate,btype)>
    <!ELEMENT custno (#PCDATA)>
    <!ATTLIST custno type CDATA #FIXED "string" size CDATA #FIXED "8">
    ... content left out for brevity
    <!ATTLIST address type CDATA #FIXED "bin.hex" size CDATA #FIXED "4">
    ... content left out for brevity
    <!ATTLIST billrate type CDATA #FIXED "number"
    size CDATA #FIXED "7" precision CDATA #FIXED "2">
]>

The DTD describes the data format and thus allows the client application to dynamically recreate the data source by querying the type, size and precision attributes (note: although these are XML compliant types, they are not part of an XML data standard). The client application, whether or not it's a VFP app, can look at the first record of the data to retrieve the format for all of the fields in the table, then actually create that table on the fly.

If you're using wwXML, the DTD is used to create the cursor for you automatically. However, the client could also be a Java application that reads the XML, creates a new table via JDBC, and updates the new table with the data received. Or, it could be a VB app, using ADO to do the same. In these cases, the client applications would be responsible for parsing the XML above into the tables (or other structures) manually, or using a local implementation of the functionality that wwXML provides to Visual FoxPro.

The current version of wwXML uses a custom DTD that is based on an early published third-party XML data specification. However, DTDs are on the way out, and a future version of wwXML will include support for creating an XML-DATA compliant schema. Schema output is not implemented yet, due to the inability to embed a schema directly into an XML document - this feature is slated for the next rev of the MSXML parser.

wwXML can also output the same data to ADO-compatible XML. ADO XML is useful if you plan to share data with another Windows application that uses ADO for its internal data access. To grab the same data in ADO format:

http://www.west-wind.com/XMLCursor.wwd?display=ADO

On the server, only a couple of lines of code are changed to accommodate this behavior. First, we check for the Display QueryString variable, then we call the appropriate wwXML method to convert the data:

llADO = (Request.QueryString("Display") = "ADO")

and

IF !llADO
    lcXML = loXML.CursorToXML("customers","customer")
ELSE
    lcXML = loXML.CursorToADOXML()
ENDIF

ADO-compatible XML is entirely attribute based, which is rather messy to look at and causes extra parsing overhead. However, because ADO recordsets can directly load this XML, it's ideal for ADO clients, since using rs.Open() to read in the XML is very fast. For generic XML applications that support clients other than ADO, I would recommend that you stay away from this non-standard ADO XML implementation.

Pulling XML from the Web

Once you've generated XML on the server, the next step is to retrieve the XML in a client-side application. Pulling and posting data from the Internet is a very powerful concept, and is the cornerstone for building distributed applications, whether you run a Visual FoxPro application as the client or use a browser like Internet Explorer. The good news is that this task is easy, with a number of tools available to perform the HTTP access.

For Visual FoxPro client applications, the wwXML class includes a LoadURL method, which can be used to return any HTTP content directly into a string with a single line of code. Once you have the string, you can then use the XMLToCursor method to convert the string into a VFP cursor:

owwXML = CREATE("wwXML")
lcXML = owwXML.LoadUrl([https://www.west-wind.com/XMLCursor.wwd?Company=A])

owwXML.XMLToCursor(lcXML,"TCursor") && Create Cursor TCursor

Pretty easy, huh? LoadUrl can download any HTTP data, not just XML. It also allows you to post data to the server, as we'll see a little later. XMLToCursor() then goes to work on the XML document object, figuring out the document structure and creating a cursor TCursor (the second parameter) on the fly. If the cursor doesn't exist, as is the case here, the cursor is created based on the DTD that accompanies the XML document. If a cursor named TCursor exists already, the data is appended instead. In order for a cursor to be created on the fly, a DTD is required, and it must follow the format described earlier. If no such DTD exists, you have to provide the cursor yourself, either by creating it in your code, or providing a table from your application.

The download in wwXML uses WinInet system API functions from within Visual FoxPro code, which is lightweight and allows good control over all aspects of the HTTP process. It's based on the wwHTTP class, which is provided as part of the wwXML class. wwHTTP provides access to all of HTTP's functionality, including security, posting, and custom content types.

If you'd rather use a COM component, or you want to use the parser manually to process the XML from the site, you also can use the XMLDOM parser to download an XML document object:

oxml = CREATEOBJECT('Microsoft.XMLDOM')
oXML.async = .f.
oXML.load("http://www.west-wind.com/XMLCursor.wwd?Company=A";)

*** Must check document for validity
IF !EMPTY(oXML.ParseError.Reason)
    RETURN .F.
ENDIF

owwXML = CREATEOBJECT("wwXML")
owwXML.XMLToCursor(oXML,"TEMPFILE")

Here, the XMLDOM's Load method is used to download the data, yielding an XMLDOM object, which can also be passed to the XMLToCursor method. The Load method downloads the XML, and immediately tries to load it into the MSXML parser as an XML document. Load works well for simple things, but it doesn't support POSTing data to the server, doesn't refresh if the URL is requeried (caching). Nor can it deal with Basic Authentication security to login users where security is required.

Manual Parsing

Of course, using wwXML is optional. You can also manually parse the XML code and use it as you see fit. The following code walks through all the records of the table and echoes back all fields to the VFP console generically:

oxml = CREATEOBJECT('Microsoft.XMLDOM')
oXML.async = .f.
oXML.load("http://www.west-wind.com/XMLCursor.wwd?Company=A";)

loRows = oXML.SelectNodes("/WebConnection/customers/customer")
FOR EACH loRow IN loRows
    FOR EACH loField IN loRow.ChildNodes
        ? loField.Text
    ENDFOR
    ? "---------"
ENDFOR

In this case, I'm relying on the actual structure of the document by knowing beforehand the names of the document, table and rows for use with the XSL method SelectNodes. It gets a little more confusing if you want to generically access nodes:

loRows = oXML.DocumentElement.ChildNodes(0).ChildNodes

Manual XML parsing is not terribly complex, but it is tedious and repetitive. It takes a trip to the XML reference before getting it right the first few times, and even now I have to look at some working code for reminders, and to get things working quickly. The main issue with this type of code is that it's repetitive and potentially error-prone. Using a tool like wwXML to handle things generically is much cleaner. I encourage you to use or build (if you're not using VFP) tools that wrap this type of behavior, to minimize XML hand-coding.

Objects make the world go 'round

When we build applications, business objects that contain the structure and business logic of our application are key in making things work properly. They abstract and isolate this logic into a central structure that is easy to maintain and administer. Because objects often describe a particular piece of information about an application, they also make great containers for data, depending on how the business objects are structured.

For example, on my Web site most business objects include member objects that hold the actual data. An inventory object for my Web Store application has an oData member that contains the actual item information - in this case, simply the fields of the inventory item record added to the Item object with SCATTER NAME loItem.oData MEMO. This works for local data pulled from Visual FoxPro tables, or for data pulled from a SQL backend like SQL Server or Oracle via ODBC or ADO/OleDb. Once loaded, to get access to the data, I can use the following code:

loItem = CREATEOBJECT("cItem")
loItem.Load( 1241 )  && Customer PK
? loItem.oData.Sku? loItem.oData.Descript

Methods on the object can then be used to perform operations on this business object. If I make a change to the item description and save the change, I'd use:

loItem.oData.Descript = "West Wind Web Connection 3.20"
loItem.Save()

Since this object is self-contained and has its own logic to deal with typical tasks of loading and saving, it's very easy to use, and can be passed around as a message inside the local application, or even in COM method calls across application boundaries.

Figure 3 - The same URL with the XML parameter to render the object as an XML document
Figure 3 - The same URL with the XML parameter to render the object as an XML document

Wouldn't it be nice if you could also pass an object like this around over the Web? The answer to doing this lies in persisting the object member data into XML, and then passing this XML over the wire. wwXML supports this functionality using the ObjectToXML method. On my Web store site most URLs can be accessed by adding a special flag to the HTML display URLs. For example, to display item data for a product on the Web site I can use the following URL:

http://www.west-wind.com/wwstore/item.wws?sku=WCONNECT

This gives me an HTML page. The Web application renders the data using a cItem object instance referenced in template tags (like <%= oItem.oData.Descript %> for example) that are expanded on the HTML page. I can also get that same information in XML format by adding &display=XML to the end of the URL:

http://www.west-wind.com/wwstore/item.wws?sku=WCONNECT&display=XML

Now, when the data is returned, the HTML template is not called, and the data is generated using wwXML's ObjectToXML method instead:

FUNCTION Item
    LOCAL loItem
    PRIVATE oItem, pcSKU
    
    loItem = CREATE([cItem])
    pcSKU = UPPER(Request.QueryString("Sku"))
    
    IF EMPTY(pcSKU) OR ;
        !loItem.GetItemBySku(pcSku)
        THIS.ErrorMsg("Invalid Item")
        RETURN
    ENDIF
    
    oItem = loItem.oData
    
    IF THIS.nResultMode = 2
        loXML = CREATE("wwXML")
        loXML.cDocRootName = "wwstore"
        lcXML = loXML.ObjectToXML(oItem,"item")
        
        Response.ContentTypeHeader("text/xml")
        Response.Write(lcXML)
        RETURN
    ENDIF
    
    Response.ExpandTemplate( Request.GetPhysicalPath() )

Everything runs through the cItem business object. The decision to generate XML is set in the nResultMode flag, and is checked in the startup of the of the Process object, which handles the Web request (in the Process method):

*** Support XML retrieval of data on select request
THIS.nResultMode = IIF(Request.QueryString("Display") = "XML",2,0)

nResultmode is then forwarded to all request methods. If it's set to 2, wwXML is used to create XML from the object. Otherwise, HTML is requested and the ExpandTemplate method of the Response object is used to evaluate the HTML template containing the <% %> script tags. If you used ASP instead of Web Connection, this business logic, as well as the check for the display mode, would be embedded inside the ASP page or a COM component that returns the result either in HTML or XML formats.

The wwXML syntax is very simple: You create the object and call the ObjectToXML method. The XML generated is shown in Figure 3. Note that we decided to return only the oData member (oItem = loItem.oData) rather than the entire Item object, since the main object's (the generic business object properties) data is not appropriate here.

If I want to retrieve this data from a VFP client application that has this Item object available, I can now run the following:

loXML=CREATE("wwXML")
lcXML=loXML.LoadUrl("http://.../item.wws?sku=wconnect&display=XML")

loItem = CREATE("cItem")
loItem.GetBlankRecord()  && Init oData member

loXML.XMLToObject(lcXML,loItem.oData)

? loItem.oData.Descript
? loItem.oData.Price

Note that I pass loItem.oData as a second parameter to XMLToObject, as the object that is to be populated with the data from the XML. The passed object is used as a template for pulling the data from the XML, by walking through the object properties and looking for matching elements in the XML document. You can also optionally have the server side generate a DTD and let the client side assemble a generic container object that only contains the object's properties. This may be useful in some scenarios, but most commonly an object that receives the incoming data will likely exist on the client application.

Pretty slick isn't it? With a handful of lines of code, you can persist an object from the server down to a client very, very easily! Note also that the XML is version-independent - the client application determines which data is used, so that if the server decides to add a new fields it won't break our client application. We've essentially decoupled the client and server through the intermediary XML. You can swap either end of the transaction with a different client or server and the other side would never know the difference!

Complex objects and hierarchical XML

If I take object persistence a step further, I can have more complex objects that combine multiple objects together. For example, this same Web Store application has an invoice object that has its own oData member containing the invoice header information, and also contains an oCustomer and oLineItems object for the detail info. When I load an invoice object, I can now access each of the subobjects through the invoice object, since it internally manages the subobjects as part of the load operation:

loInv = CREATEOBJECT("cInvoice")
loInv.Load( 100 )  && Invoice PK
? loInv.oData.InvDate   && Invoice Data
?  loInv.oData.InvTotal
? loInv.oCustomer.oData.LastName  && Customer Data
? loInv.oCustomer.oData.Company
? loInv.oLineItems.aRows[1].SKU   && LineItem Data
? loInv.oLineItems.aRows[1].Description
? loInv.oLineItems.aRows[1].ItemTotal

The invoice object has its own oData member, as does the oCustomer member object. loInv is simply a container that holds this object. The oLineItems member contains all of the lineitem detail in an object that has a member array of all the lineitem objects containing the actual lineitem data, which again simply maps to fields in the lineitems table.

This object is very portable! You can take the entire object and simply pass it as a reference to a method or function, and let that code access all of the properties. The calling application (in this case an online e-Commerce app and an offline invoice manager application using the same object) can simply use the business object methods such as Load, Save, and New to manage the object.

Because of this structured object approach, it's also possible to use this object on a Web page in an HTML template. In my application, template pages use syntax like:

<%= oInv.oCustomer.oData.LastName %>

to embed the actual data into the HTML form as shown in Figure 4.

Figure 4 - The invoice business object displayed in HTML format. All the elements displayed on this HTML form are using template expansion with ASP syntax to render the business object properties
Figure 4 - The invoice business object displayed in HTML format. All the elements displayed on this HTML form are using template expansion with ASP syntax to render the business object properties

On the server side, the invoice is rendered either in HTML format or in XML format, according to the QueryString display mechanism discussed earlier. There's one difference, though: This object is considerably more complex and requires a hierarchical XML document. To generate the XML on the server the following code is used:

lnPK = VAL( Request.QueryString("PK") )
IF lnPK = 0
    THIS.ErrorMsg("Invalid PK")
    RETURN
ENDIF

loInv = CREATE("cInvoice")
IF !loInv.Load(lnPK)
    THIS.ErrorMsg("Invalid Invoice")
    RETURN
ENDIF

loXML = CREATE("wwXML")
loXML.lRecurseObjects = .T.
loXML.cDocRootName = "wwstore"
lcXML = loXML.ObjectToXML(loInv,"invoice")

Response.ContentTypeHeader("text/xml")
Response.Write(lcXML)

Note the lRecurseObjects property, which is used to make sure that all the surrogate objects are also created into the XML:

<?xml version="1.0"?>
<wwstore>
    <invoice>
        <calias>wws_invoice</calias>
        <cerrormsg></cerrormsg>
        <cfilename>wws_invoice</cfilename>
        ...
        
        <ocustomer>
            <calias>wws_customers</calias>
            <cerrormsg></cerrormsg>
            <cfilename>wws_customers</cfilename>
            ...
            <odata>
                <address>32 Kaiea Place</address>
                <city>Paia</city>
                <company>West Wind Technologies</company>
                <countryid>US</countryid>
                <email>rstrahl@west-wind.com</email>
                <entered>1999-11-19</entered>
                ...
                <zip>96779</zip>
            </odata>
        </ocustomer>

        <odata>
            <cc>4123 1234 1234 1231</cc>
            <ccexp>08/2002</ccexp>
            <cctype>VI</cctype>
            ...
            <tax>29.96</tax>
            <weight>2</weight>
        </odata>
        <olineitems>
            <ncount>2</ncount>
            <arows>
                <arows_item>
                    <descript>Web Connection</descript>
                    <invpk>110811</invpk>
                    <pk>106074</pk>
                    <price>699</price>
                    <qty>1</qty>
                    <sku>WCONNECT</sku>
                    <weight>0</weight>
                </arows_item>
                <arows_item>
                    <descript>HTML Help Builder</descript>
                    ...
                </arows_item>
            </arows>
        </olineitems>
    </invoice>
</wwstore>

This time around, the object is generated with all of the base properties of the business objects - we get properties that are not required to be transferred over the wire, such as the data filename, the Alias, the error message property, and so on. These are base business object properties, which although superfluous, don't hurt anything (other than making the result a little bigger). Incidentally, you can hide these properties during export by using the wwXML::cPropertyExclusions property.

Take a moment to think about how powerful this is: With a couple of lines of code you've generically persisted a rather complex and nested business object to XML! You can now transfer this object over the wire and - you guessed it - reassemble it on the client side just as easily. In fact, the Web store application includes an offline invoice manager, which retrieves orders placed online. The offline application can then be used to process credit card transactions (also using HTTP and XML) and send order confirmations to customers.

Figure 5 - The offline Invoice Manager uses the same business objects used on the server, and receives invoices via invoice objects sent over the wire as XML. Fields in this form are bound to the business object, such as THISFORM.oInv.oData.InvDate and THISFORM.oInv.oCustomer.oData.Company.
Figure 5 - The offline Invoice Manager uses the same business objects used on the server, and receives invoices via invoice objects sent over the wire as XML. Fields in this form are bound to the business object, such as THISFORM.oInv.oData.InvDate and THISFORM.oInv.oCustomer.oData.Company.

The offline manager has a download link, which runs in a loop to download any unprocessed invoices from the server. The relevant import code for an individual invoice looks like this:

loXML = CREATE("wwXML")
lcXML = loXML.LoadUrl("https://www.west-wind.com/wwstore/admin/MaintShowInvoices.wws?Action=Show&;Display=XML&PK=110811",,"username","password")

loInv = CREATE("cInvoice")
loInv.Load(0)  && Load empty invoice & subitems

loXML.lRecurseObjects = .T.
loXML.XMLToObject(lcXML,loInv)

? loInv.oCustomer.oData.LastName
? loInv.oCustomer.oData.Company

? loInv.oData.InvDate
? loInv.oData.InvTotal

? loInv.oLineItems.ARows[1].Sku
? loInv.oLineItems.ARows[1].Descript

Again, notice how little code is involved here - 4 lines of code to download and import the object. However, in order for the import of this hierarchical object to work, a couple of pre-requisites are required. The objects must exist on the client side in order for wwXML to be able to figure out the structure of the object to import to - hierarchical objects cannot be dynamically created from an export DTD. Furthermore, the object structure must be prefilled with empty values of the proper types in order for the import to work.

member objects imported to cannot be NULL or otherwise undefined, as that would prevent them from being parsed and filled. wwXML requires an object structure, as it works by walking through the object and looking for matching elements in the XML document. In the case of the LineItem array, at least the first item of the array must be pre-filled with the proper type - in this case, an Item object.

Notice the special call to loInv.Load(0). This method call actually handles creating the properly formatted empty objects. If we look at the business object code's Load method we'll see the following:

IF lnPK = 0
    THIS.GetBlankRecord()
    THIS.oCustomer = CREATE("cCustomer")
    THIS.oCustomer.GetBlankRecord()
    
    *** Create an empty item list
    THIS.oLineItems = CREATE("cItemList")
    loLItem = CREATE("cLineItems")
    loLItem.GetBlankRecord()
    THIS.oLineItems.aRows[1] = loLItem.oData
    RETURN .F.
ENDIF

Each object is pre-initialized as an empty object. This type of behavior is not only useful for wwXML's import parsing, but also for displaying the business objects in forms. For example, in the invoice manager form the actual field ControlSource values are bound to the object's properties. NULL values would cause all sorts of problems for the databinding.

POSTing data to the server

So far, I've shown how you can get data downloaded from the server and use it on the client side. You can also go the other way! Let's look at the offline manager application, which is actually in use by several people in our organization. West Wind Technologies is associated with EPS Software, and EPS Software may take orders at their offices in Houston. However, West Wind manages all product sales, so all credit card transactions are processed by West Wind in Hawaii. The idea is that the folks in Houston can use the Invoice Manager to enter orders at their offices, and post those invoices to the West Wind Web Server located in Oregon. West Wind can then download the orders posted to the Web server, in the same way as online orders placed directly on the Web site. The concept could be extended to allow third party vendors to also post orders in XML format directly to the West Wind Web site. This is a good example of the benefit of a distributed application in a small business environment!

We already know how to create the invoice XML easily - the process in the Invoice Manager client application is identical to what's happening on the server. What's different now is that we're sending data to the server:

* frmInvoice :: UploadInvoice
LPARAMETER lnPk
LOCAL loXML, loIP, lcResult, loInv

IF !EMPTY(lnPk)
    loInv = CREATE("cInvoice")
    IF !loInv.Load(lnPK)
        RETURN .F.
    ENDIF
ELSE
    loInv = THISFORM.oInv
ENDIF

*** Export invoice to XML hierarchically
loXMl = CREATE("wwXML")
loXMl.lRecurseObjects = .T.
lcXML = loXMl.ObjectToXML(loInv)

lcResult = loXML.LoadUrl(THIS.oConfig.cUploadINvoiceWebLink, lcXML, THIS.oConfig.cAdminUsername, THIS.oConfig.cAdminPassword)

*** check for errors
IF lcResult # "OK"
    IF loXML.lError
        MESSAGEBOX(loXML.cErrorMsg,48,WWSTORE_APPNAME)
    ELSE
        MESSAGEBOX(lcResult,48,WWSTORE_APPNAME)
    ENDIF
ELSE
    MESSAGEBOX("Invoice uploaded.",64,WWSTORE_APPNAME)
ENDIF

The only new thing that happens here is that when we call wwXML::LoadUrl(), we're passing the XML generated from the invoice export as the second lcXML parameter, which is POSTed to the Web server. LoadURL() posts data in raw format and doesn't URLEncode the string. The server knows how to pick up this raw XML buffer. This simple bit of code handles posting an invoice from the client to the server. On the server, the code is straightforward:

FUNCTION SubmitXMLOrder

    *** Security Check
    IF !THIS.Login(Config.cAdminUser)
        RETURN
    ENDIF
    
    *** Retrieve XML input if passed
    lcXML = Request.FormXML()
    
    *** Create an Invoice object and initialize
    loInv = CREATE([cInvoice])
    loInv.Load(0)
    
    *** Import the object
    loXML = CREATE("wwXML")
    loXML.lRecurseObjects = .T.
    loXML.XMLToObject(lcXML,loInv)
    
    *** Check if data is Ok
    IF !(loInv.oData.Pk</a> # 0 AND <a href="http://loInv.oCustomer.oData.pk">loInv.oCustomer.oData.pk # 0)
        Response.Write( "Error: Invalid Order Info")
        RETURN
    ENDIF
    
    *** Must fix up invoice for PK conflicts
    loInv.CheckInvoiceForPkConflict()
    
    loInv.Save()
    
    Response.Write("OK")

Keep in mind that these types of replication scenarios require thought about synchronization: You may run into duplication of primary keys. This invoice object includes a method that checks to see whether an invoice PK exists already, and if necessary, reassigns the PK for the invoice and updates the lineitems accordingly. For the customer, if a PK exists already, it double checks to see if the name and address match the original record. If it doesn't, a new customer record is created instead. Conflict resolution is not always easy, as this type of online/offline application becomes an exercise in replication, along with all the issues related to this complex topic of synchronization of data.

More power to you!

I hope this article has given you some ideas on how you can utilize XML from Visual FoxPro client applications. Even if you're not using Visual FoxPro, you can probably see that with the right tools for your development language of choice, you can perform powerful transfers of data with minimal coding effort. As the examples above show, you need only a few lines of code to handle convert tables and objects into XML, transfer that data over the wire, and reassemble the XML back into the appropriate data or object structure.

I'm sure that you can think of some useful applications for this stuff. Using these strategies, you can very easily build powerful applications with existing business objects, which can be transferred painlessly over the wire. The possibilities are endless.

Also remember that XML can be useful for many things, even in non-distributed applications. By persisting objects and tables as XML, you can pass data around in applications, and even save object state into a table's memo field.

In the next issue, we'll look at how to build XML-based services that provide data access and remote code activation to a variety of clients generically. I'll talk more on how to access and consume XML in browser applications, as well as fat client applications, using the same application logic. We'll also look at SOAP and how it figures in that space. Until then, give it a shot and put what you've learned to good use.