[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]

Client applications communicate with the server via HTTP using XML as the messaging mechanism. Data traveling over the wire will typically be data from tables or custom representations of abstract structures such as business objects. There are no rules about the format of messages - the application determines the XML format used. Clients must have access to an XML parser (or at least some mechanism to parse XML) and an HTTP client that can access content from the Web and POST data to a Web server. For Windows based machine these requirements are best met with Internet Explorer 5.0 which includes both an XML parser as well as an HTTP component in the XMLHTTP object that is included as part of the MSXML parser. This covers access from client applications like Visual FoxPro and Visual Basic with minimal amounts of code. For Visual FoxPro clients you can also use native code that uses WinInet via the shareware West Wind Internet Protocols class, which gives you more flexibility in accessing HTTP.

Other platforms will require their own tools. Netscape browsers for example, can use Java XML parsers and HTTP components, since it doesn't include native XML support. Other platforms such as Unix, Linux and Mac also can use XML and HTTP components in their development languages of choices - tools exist for almost every imaginable platform.

The client is responsible for displaying the user interface at the very least, but may also provide local validation of data and maybe even an offline operation model when the data provided by the server is disconnected. The client communicates with the server over HTTP by sending messages in XML format to the server. Data is POSTed to the Web server and the Web server picks up the XML input.

The backend Web application picks up the XML and processes the request. Commonly the XML will be converted back into some native format such as a table or object and then applied against the database or business object engine. The backend is responsible for reading and writing data to and from the database.

When the request is done an XML result is returned. The XML is sent back over the wire as part of the original HTTP request (a single HTTP request can both POST data to the server and retrieve a result in the same operation) to the client. The client can then retrieve the XML and use it on the client side to either display it or perform other operations on it.

In this scenario XML becomes a true messaging format - all communication occurs using XML messages that provide both operational instructions as well as the actual data required for the application

Building tools to work with XML

Ok, it's time to put what we've learned to use in Visual Foxpro.

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.

Intoducing wwXML

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.

 
Generic tools let you avoid having to 
write repetitive XML parsing code. 
wwXML provides imports and exports 
for Visual FoxPro tables and objects 
without writing any XML code
.
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).

    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. 

Figure 2 - 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.

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. 

 

Schemas versus DTDs 
The purpose of DTDs and schemas both is to provide structure information about the XML document and a quick way to check document validity without having to parse into the document first with manual checks. They also provide the ability to add custom attributes with default values and data typing, which is crucial to provide information about the data types in an XML document to client applications.

DTDs are part of the XML 1.0 specification and widely supported by XML parsers. Unfortunately DTDs are not XML based, don't support namespaces (required to avoid naming conflicts) and they are not extensible. Schemas address all of these issues with an XML based implementation that provides all the features of DTDs with all the extensibility of XML, but they are not officially part of the XML standard yet. However, MSXML 1.0 has basic support for external schemas. The latest version (version 3 is in late beta) also adds support for embedded schemas, which are vital to dynamic XML data generated on the fly as in this article. 

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 (chaching). 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")
     
    IF !EMPTY(oXML.ParseError.Reason)
       MessageBox(oXML.ParseError.Reason)
       RETURN
    ENDIF
     
    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:
MSXML Bug
Microsoft shipped a bugged version of MSXML with the release version of Windows 2000 and Internet Explorer 5.01. This bug causes Incorrect Function errors (OLE Error 1) in Visual Foxpro to occur whenever an invalid node is accessed in an XML document. It also causes the Load and LoadXML methods to fail if the XML document is not valid. This bug has been acknowledged and fixed by Microsoft, and can be taken care of by installing a patch file

wwXML uses the MSXML parser for importing data, and unless you run this patch, wwXML will fail upon importing any XML. wwXML includes a method IsMSXMLBug() that lets you check for the bug.

For more information, please visit: http://www.west-wind.com/webconnection/msxml.asp.  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. 

[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]