************************************* * * * DB/C Newsletter * * November 2001 * * * ************************************* News and Comments The beta testing of DB/C DX 12 is progressing well. Unless we run into some unforeseen issues, DB/C DX 12 will be released in December. XML continues to grow in popularity. It is being used in a variety of places, most somehow related to ecommerce. We've discussed XML several times here, including the short tutorial in the September 1999 DB/C Newsletter. Several DB/C users have started using XML in one way or another. The first thing that most encounter is the need for a set of routines to build and traverse XML data. This month's newsletter describes a DB/C class that provides such features. I'm the author of that DB/C XML program. It's been a while since I've written any DB/C programs, so I know the code isn't particularly pretty. I'm sure the code contains bugs, it doesn't handle error situations, and it needs an import parser to verify the XML validity. If you fix any bugs or make any enhancements, we would appreciate it if you would send us those changes so we can incorporate them in the source files that we provide in the Sample Code place at www.dbcsoftware.com. Here's a useful tip. While I was testing the code for this month's newsletter, I noticed that each time I started the runtime (dbcc.exe or dbc.exe) there was a delay of a few seconds. This delay is really annoying. If you are testing in an environment where you have exclusive use of the files, you can make the delay disappear by specifying dbcdx.file.sharing=off in the dbcdx.cfg file. don.wills@dbcsoftware.com ****************************************************************************** DB/C and XML At its most basic level, XML is a very simple method of representing hierarchical data structures. There are essentially three concepts in an XML data structure: elements, attributes and text. Elements are either standalone, or they contain sub-elements and text. In addition, one or more uniquely named attributes are associated with an element. Here are some text representations of an element: The first example above is an element that contains only the element name (or tag). It contains no sub-elements or attributes. The date="15NOV2001" field is an attribute that is associated with the element. The second and third examples are logically the same. When the '/' is at the end of an element, it signifies that there are no sub-elements. When the '/' is before the element name, it signifies the end of the sub-elements of this element. Here is an example XML element from the September 1999 DB/C Newsletter: A56223 1999-09-30 ABC Video 100 Main Street Ames Iowa 55555 Bill Riley Acme Supply POBox 123 15 Broadway Chicago IL 66666 Joe
5 431321-2 Titanic (DVD) 10 744311-0 Star Wars: Episode 1 (VHS)
Programmers need to be able to read and write XML data. Generally, the XML data is stored in files. Some XML files contain just a single element (like the example above), whereas other XML files contain multiple separate XML elements. When a file will contain multiple elements, it is usually easier just to deal with the XML elements one at a time. Thus, the two basic needs for a programmer to deal with XML are: 1. to export an XML element from the program to an output stream or file 2. to import an XML element into the program from in input stream or file When creating a set of routines to deal with the XML data while it is inside the program, the most important design issue is this: How are elements, attributes and text represented? In programming languages like C++ and Java, it is simple and obvious - each element, attribute and text chunk is an object. In DB/C it is not that simple. In the DB/C language, a CLASS is a 'heavy' entity. That is, a lot of baggage goes along with each OBJECT that is instantiated from a CLASS. (The reason for this is that a CLASS is essentially equivalent to a LOADMOD (.DBC) which is large and contains a lot of state information.) Thus, when many hundreds or thousands of OBJECTS exist in the DB/C runtime, performance will be unacceptable. As an XML element can contain hundreds of sub-elements, attributes and text chunks, a different representation is necessary for representing an XML element in DB/C. The design I've chosen for the XML code is that a single OBJECT represents an XML element along with its attributes, sub-elements and text chunks. Associated with this OBJECT is set of positions that are used to traverse through the attributes and elements. These positions are conceptually similar to the file position information associated with FILE, IFILE and AFILE variables. Inside the XML OBJECT, I chosen to represent the data in exactly the same format in which it is imported and exported - a string of characters. The code assumes that the element will fit into a single DIM variable, thus the maximum number of characters in an element is limited by the maximum size of a DIM variable which is currently 65500 characters. The DB/C XML code is contained in an CLASS DEFINITION for the XMLCODE class. The XMLCODE.PRG file contains this CLASS DEFINITION. The routines in this program are enclosed in several VERBS. The XML.DEF file contains these the VERB definitions. The source files can be found at the Sample Code place at www.dbcsoftware.com. Instead of trying to describe the routines in XMLCODE.PRG, it's easier to provide examples. The XML.DEF file follows the examples. Here is a sample program that uses the XMLCODE CLASS to create the PurchaseOrder XML element above: INC XML.DEF PO OBJECT OUT CHAR 8000 XMLMAKE PO, "PurchaseOrder" . create PO object with node name XMLADDNODE PO, "OrderNumber", SETPOS . new sub-element and set position XMLADDTEXT PO, "A56223" . add text below OrderNumber XMLSETPARENT PO . set position to PurchaseOrder XMLADDNODE PO, "OrderDate", SETPOS . new sub-element and set position XMLADDATTR PO, "datetype", "ISO" . add attribute to OrderDate XMLADDTEXT PO, "1999-09-30" . add text below OrderDate XMLSETPARENT PO . set position to PurchaseOrder XMLADDNODE PO, "Nontaxable" . add sub-element XMLADDNODE PO, "Purchaser", SETPOS . add sub-element and set position XMLADDNODE PO, "Name", TEXT="ABC Video" . add sub-element and text below XMLADDNODE PO, "Street", TEXT="100 Main Street" XMLADDNODE PO, "City", TEXT="Ames" XMLADDNODE PO, "State", TEXT="Iowa" XMLADDNODE PO, "PostalCode", TEXT="55555" XMLADDNODE PO, "Contact", TEXT="Bill Riley" XMLSETPARENT PO . set position up to PurchaseOrder XMLADDNODE PO, "Vendor", SETPOS XMLADDNODE PO, "Name", TEXT="Acme Supply" XMLADDNODE PO, "Street", TEXT="POBox 123" XMLADDNODE PO, "Street", TEXT="15 Broadway" XMLADDNODE PO, "City", TEXT="Chicago" XMLADDNODE PO, "State", TEXT="IL" XMLADDNODE PO, "PostalCode", TEXT="66666" XMLADDNODE PO, "Contact", TEXT="Joe" XMLSETPARENT PO XMLADDNODE PO, "Details", SETPOS XMLADDNODE PO, "Item", SETPOS XMLADDNODE PO, "Quantity", TEXT="5" XMLADDNODE PO, "Number", TEXT="431321-2" XMLADDNODE PO, "Desc", TEXT="Titanic (DVD)" XMLSETPARENT PO XMLADDNODE PO, "Item", SETPOS XMLADDNODE PO, "Quantity", TEXT="10" XMLADDNODE PO, "Number", TEXT="744311-0" XMLADDNODE PO, "Desc", TEXT="Star Wars: Episode 1" XMLEXPORT PO, OUT . move XML from PO to OUT FILE FILE VAR=8000, TEXT SEQ FORM "-1" PREP FILE, "XMLOUT.TXT" WRITE FILE, SEQ; *+, OUT . for example, write OUT STOP Here is a sample program that uses the XMLCODE CLASS to read and print the PurchaseOrder XML element above: INC XML.DEF PO OBJECT IN CHAR 8000 C1 CHAR 40 C2 CHAR 40 C3 CHAR 40 SEQ FORM "-1" FILE FILE VAR=8000, TEXT OPEN FILE, "XMLOUT" READ FILE, SEQ; IN . for example, read IN XMLMAKE PO . create empty PO XMLIMPORT PO, IN . import the XML data into PO XMLGETNAME PO, C1 . the tag name of the top element GOTO ERROR IF (C1 != "PurchaseOrder") . should be PurchaseOrder DISPLAY "Start of Purchase Order" LOOP . loop through each sub-element XMLGETNEXT PO, C1, SETPOS . C1 is name, set pos down 1 level BREAK IF OVER . no more sub-elements CONTINUE IF LESS . ignore any text at this level SWITCH C1 CASE "OrderNumber" XMLGETNEXT PO, C2 . assume the sub-element is text DISPLAY "Order number: ", *LL, C2 CASE "OrderDate" DISPLAY "Order date: "; XMLGETATTR PO, C2, C3 . get the attribute name and value IF NOT OVER . OVER means no attributes DISPLAY "(", *LL, C2, "=#"", *LL, C3, "#") "; ENDIF XMLGETNEXT PO, C2 . assume the sub-element is text DISPLAY *LL, C2 CASE "Purchaser" DISPLAY "Customer:" CALL DISPLAYNAMEADDR . print name and address CASE "Vendor" DISPLAY "Vendor:" CALL DISPLAYNAMEADDR . print name and address CASE "Details" CALL DETAILS ENDSWITCH . ignore any unknown sub-elements XMLSETPARENT PO . set position up 1 level REPEAT DISPLAY "End of Purchase Order" PAUSE "2" STOP DISPLAYNAMEADDR LOOP XMLGETNEXT PO, C2, SETPOS . C2 is name, set pos down 1 level RETURN IF OVER . no more sub-elements CONTINUE IF LESS . ignore any text at this level XMLGETNEXT PO, C3 . C3 contains the text value CONTINUE IF NOT LESS . should not occur (sub-element) DISPLAY " ", *LL, C2, ": ", *LL, C3 XMLSETPARENT PO . set position up 1 level REPEAT DETAILS DISPLAY "
" . NOT IMPLEMENTED RETURN ERROR DISPLAY "ERROR!" PAUSE "2" STOP Here is the XML.DEF file which includes the documentation for the CVERBS in the XMLCODE class. . . definitions for using xmlcode class . xmlcode class module="xmlcode" xmlmake verb !make(xmlcode), =cvarlit, maxchars=nvarlit xmlfree verb !destroy xmlimport verb !method, #cvarlit xmlexport verb !method, #cvar xmlgetname verb !method, #cvar xmlgetattr verb !method, #cvar, #cvar xmlgetnext verb !method, #cvar, setpos xmladdnode verb !method, #cvarlit, setpos, text=cvarlit xmladdtext verb !method, #cvarlit xmladdattr verb !method, #cvarlit, #cvarlit xmlgetpos verb !method, #cvar xmlsetpos verb !method, #cvar xmlsetparent verb !method xmlsetchild verb !method xmlreset verb !method . . XML import, export and usage routines . . The object created by xmlmake defines a single XML element along . with all of its sub-elements and text. Three traversal positions are . associated with this object. The value of the current node traversal . position defines the current element (node) of the XML tree. The . value of the attribute traversal position defines the position of the . attribute returned by XMLGETATTR. The sub-element traversal position . defines the position of the sub-element (node or text) that is returned . by XMLGETNEXT. The current node traversal position is set by XMLSETPOS, . XMLSETPARENT, XMLSETCHILD and by the XMLGETNEXT verb if the SETPOS operand . is specified. XMLRESET resets the attribute and sub-element traversal . positions without altering the current node traversal position. . . Syntax and description (optional operands in square brackets): . . XMLMAKE [cvar,] [MAXCHARS=nvarlit] . Create an XML object. If the cvar operand is specified, the XML . object will have one element with tagname specified by cvar. If . cvar is not specified, an empty XML object is created. The maximum . number of characters that this XML object can contain is specified . by nvarlit. Default is 8000. . . XMLFREE . Free the resources associated with this XML object. . . XMLIMPORT cvarlit . Populate this XML object with nodes and text by parsing the stream . of characters contained in the required operand. . . XMLEXPORT cvar . Create the XML stream of characters from this XML object. . . XMLGETNAME cvar . Move the tag name of the current node into the destination operand. . . XMLGETATTR cvar, cvar . Move the attribute traversal position to the next attribute and . move the attribute name to the first destination operand and the . value of the attribute to the second operand. Set OVER flag and . clear the operands if there are no more attributes. . . XMLGETNEXT cvar, [SETPOS] . Move the sub-element traversal position to the next sub-element. . If the next sub-element is text, the LESS flag is set and the . text is moved to the destination operand. If the next sub-element . is a node, the LESS flag is cleared and the name of the node is . moved to the destination operand. In addition, if the LESS flag . is cleared and SETPOS is specified, the current node traversal . position is set so that the sub-element becomes the current node, . and the attribute and sub-element traversal positions are reset. . . XMLADDTEXT cvar . Add text after the last sub-element of the current element. . . XMLADDNODE cvar, [cvar,] [SETPOS] . Add an new sub-element after the last sub-element of the current . element. The tag name of the new element is specified by the . operand. The current sub-element traversal position is reset. . If the second cvar operand is specified, then a text sub-element . is added below the new sub-element. If the SETPOS operand is . specified, then an operation equivalent to XMLSETCHILD also . occurs. That is, if SETPOS is specified, the current node is . set to the sub-element just added. . . XMLADDATTR cvar, cvar . Add an attribute to the current node. The first operand is the . name of the attribute and the second operand is the value of the . attribute. The attribute and sub-element traversal positions are . reset. . . XMLSETCHILD . If the current sub-element traversal position is invalid (reset), . then the OVER flag is set and nothing else occurs. Otherwise, . the OVER flag is cleared and the three traversal positions are . pushed onto the traversal position stack. Then the current node . traversal position is set to the sub-element traversal position, . the attribute and sub-element traversal positions are reset. . . XMLSETPARENT . If the traversal position stack is empty, the OVER flag is set . and nothing else occurs. Otherwise, clear the OVER flag and pop . the three traversal positions from the traversal position stack. . . XMLGETPOS cvar . Move a string of characters that is the three traversal positions . to the destination operand. The destination variable must be at . least 15 characters long. The XMLGETPOS and XMLSETPOS verbs . are only useful if the value of the XML element does not change . between the execution of XMLGETPOS and execution of XMLSETPOS. . . XMLSETPOS cvar . Set the three traversal position to the values specified by the . operand which was set by the XMLGETPOS verb. The XMLSETPOS verb . does not operate correctly if the value of the XML element changes . between the execution of XMLGETPOS and XMLSETPOS. . . XMLRESET . Reset the current attribute and sub-element traversal positions . to the logical position before the first sub-element and attribute. . ****************************************************************************** DB/C Class Schedule Class: DB/C DX and JX Language Fundamentals Date: January 21-23, 2002 Location: Oak Brook, Illinois For information, send email to admin@dbcsoftware.com. ****************************************************************************** Subscribing to the DB/C Newsletter If you don't already have the DB/C Newsletter delivered to your email address and would like to have it emailed to you monthly, just send an email message to 'dbcnews-subscribe@dbcsoftware.com'. The newsletter will be delivered to the email address from which the message was sent.