Try OpenEdge Now
skip to main content
Working with XML
Reading XML Documents with the Simple API for XML (SAX) : Developing ABL SAX applications : Example code: reading customer data and writing a TEMP-TABLE
 

Example code: reading customer data and writing a TEMP-TABLE

This example is a SAX version of the DOM example described in Reading and Writing XML with the Document Object Model (DOM). The example reads an XML file containing the Customer table from the Sports database and writes the data to a temp-table. The example uses qname, assumes there is no namespace prefix, and, for clarity, omits code for transaction scoping and validation. The SAX driver procedure, i-sax2d.p, is shown here:

i-sax2d.p

DEFINE VARIABLE hParser  AS HANDLE NO-UNDO.
DEFINE VARIABLE hHandler AS HANDLE NO-UNDO.

/* Create the SAX-READER object */
CREATE SAX-READER hParser.

/* Run the persistent procedure that contains the callbacks */
RUN "i-sax2h.p" PERSISTENT SET hHandler.

/* Give the SAX-READER the handle to the persistent procedure */
hParser:HANDLER = hHandler.

/* Give the SAX-READER the info on the file to parse. This XML file does not
   use namespaces. */
hParser:SET-INPUT-SOURCE("FILE", "i-sax2.xml").
hParser:SAX-PARSE( ) NO-ERROR.

/* By the time SAX-PARSE returns, the callbacks have been called as many
   times as necessary and we're done processing the XML document (or there
   was an error) */
IF ERROR-STATUS:ERROR THEN DO:
IF ERROR-STATUS:NUM-MESSAGES > 0 THEN
/* unable to begin the parse */
MESSAGE ERROR-STATUS:GET-MESSAGE(1) VIEW-AS ALERT-BOX.
ELSE
/* error detected in a callback */
MESSAGE RETURN-VALUE VIEW-AS ALERT-BOX.
END.
ELSE
MESSAGE "Document parsed successfully" VIEW-AS ALERT-BOX.

DELETE OBJECT hParser.
DELETE PROCEDURE hHandler.
i-sax2.xml is the associated XML document.

i-sax2.xml

<?xml version='1.0' ?>
<Customers>
<Customer Name="Lift Line Skiing" Cust-num="1">
<Country>USA</Country>
<Address>276 North Street</Address>
<Address2></Address2>
<City>Boston</City>
<State>MA</State>
<Postal-Code>02114</Postal-Code>
<Contact>Gloria Shepley</Contact>
<Phone>(617) 450-0087</Phone>
<Sales-Rep>HXM</Sales-Rep>
<Credit-Limit>66700</Credit-Limit>
<Balance>42568</Balance>
<Terms>Net30</Terms>
<Discount>35</Discount>
<Comments>This customer is on credit hold.</Comments>
</Customer>
<Customer Name="Hoops " Cust-num="3">
<Country>USA</Country>
<Address>Suite 415</Address>
<Address2>40 Grove St.</Address2>
<City>Atlanta</City>
<State>GA</State>
<Postal-Code>02112</Postal-Code>
<Contact>Michael Traitser</Contact>
<Phone>(617) 355-1557</Phone>
<Sales-Rep>HXM</Sales-Rep>
<Credit-Limit>75000</Credit-Limit>
<Balance>1199.95</Balance>
<Terms>Net30</Terms>
<Discount>10</Discount>
<Comments>This customer is now OFF credit hold.</Comments>
</Customer>
</Customers>
Note: There is no DTD or XML Schema and no use of namespace prefixes. The lack of a DTD or XML schema means that the handlers need to validate the document, but this example omits that validation for the sake of clarity.
i-sax2h.p is the handler procedure.

i-sax2h.p

DEFINE VARIABLE hBuf              AS HANDLE    NO-UNDO.
DEFINE VARIABLE hDBFld            AS HANDLE    NO-UNDO.
/* Variable in which to accumulate all the text data for one element coming
   in through potentially multiple calls (per element) to the Characters
   procedure */
DEFINE VARIABLE currentFieldValue AS CHARACTER NO-UNDO.
/* Simple-minded state machine – the code makes minimal use of it, but it
   could easily be used to validate the structure of the document in this
   example. */
DEFINE VARIABLE iProcessingState  AS INTEGER   NO-UNDO.

/* So we can create new records*/
DEFINE TEMP-TABLE ttCustomer LIKE Customer.

&SCOPED-DEFINE READY-TO-START 1
&SCOPED-DEFINE GETTING-RECORDS 2
&SCOPED-DEFINE GETTING-FIELDS 3
&SCOPED-DEFINE DONE 4

hBuf = BUFFER ttCustomer:HANDLE.

PROCEDURE StartDocument:
iProcessingState = {&READY-TO-START}.
END PROCEDURE.

PROCEDURE StartElement:
DEFINE INPUT PARAMETER namespaceURI AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER localName    AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER qName        AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER attributes   AS HANDLE    NO-UNDO.

IF qName = "Customers" THEN
iProcessingState = {&GETTING-RECORDS}.
ELSE IF qName = "Customer" THEN DO:
/* Starting a new customer, so create the record */
CREATE ttCustomer.

    ASSIGN
   /* Get the fields that are in the XML doc as attributes */
      ttCustomer.CustNum =
        INTEGER(attributes:GET-VALUE-BY-QNAME("CustNum"))
      ttCustomer.Name    = attributes:GET-VALUE-BY-QNAME( "Name" )
      iProcessingState   = {&GETTING-FIELDS}.
END.
ELSE IF iProcessingState = {&GETTING-FIELDS} THEN DO:
/* Get a handle to the field whose name corresponds to the element name */
hDBFld = hBuf:BUFFER-FIELD(qName).

/* Re-init the variable in which we accumulate the field value */
currentFieldValue = "".
END.
END PROCEDURE.

PROCEDURE Characters:
DEFINE INPUT PARAMETER charData AS MEMPTR  NO-UNDO.
DEFINE INPUT PARAMETER numChars AS INTEGER NO-UNDO.

/* Get the text value of the field (hDBFld was set to the correct field
in StartElement */
currentFieldValue = currentFieldValue +
    GET-STRING(charData, 1, GET-SIZE(charData)).
END PROCEDURE.

PROCEDURE EndElement:
DEFINE INPUT PARAMETER namespaceURI AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER localName    AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER qName        AS CHARACTER NO-UNDO.

IF localName = "Customers" THEN
iProcessingState = {&DONE}.
ELSE IF localName = "Customer" THEN
iProcessingState = {&GETTING-RECORDS}.
ELSE IF iProcessingState = {&GETTING-FIELDS} THEN
hDBFld:BUFFER-VALUE = currentFieldValue.
END PROCEDURE.

PROCEDURE EndDocument:
/* Show that data made it by displaying temp-table */
FOR EACH ttCustomer:
DISPLAY ttCustomer.Name.
END.
RUN Cleanup.
END PROCEDURE.

PROCEDURE FatalError:
DEFINE INPUT PARAMETER errMessage AS CHARACTER NO-UNDO.
/* Not necessary to do anything with PRIVATE-DATA, this is just an example
     of what you could do */
SELF:PRIVATE-DATA = "FATAL".
RUN Cleanup.

/* RETURN ERROR in an error handler implicitly calls SELF:STOP-PARSING( ),
sets SELF:PARSE-STATUS to SAX-PARSER-ERROR, and raises the ABL ERROR
     condition. */
RETURN ERROR errMessage
    + "(Line " + STRING(SELF:LOCATOR-LINE-NUMBER)
    + ", Col " + STRING(SELF:LOCATOR-COLUMN-NUMBER) + ")".
END PROCEDURE.

/* This is not a SAX callback; it is just a local utility */
PROCEDURE Cleanup:
/* In case we have parsed previous documents */
hBuf:EMPTY-TEMP-TABLE( ).
END.
Note: Alternately, you could use the RETURN ERROR error-object-expression syntax and handle the resulting error object with a CATCH block in the caller. For more information on this type of structured error handling, see OpenEdge Development: Error Handling.