This example demonstrates the progressive scan technique for parsing XML. Unlike the single-call technique, the driver procedure calls SAX-PARSE-FIRST() once to initiate parsing, then calls SAX-PARSE-NEXT() in a loop to advance one XML token at a time. Between calls to SAX-PARSE-NEXT(), the business logic runs. In this case, it checks whether the callbacks have stored a complete Customer record and displays it before the next parse call. This inverts the control flow from the single-call approach: instead of callbacks driving the business logic directly, the business logic drives the parse.

This example reads customer data from i-sax2.xml (the same XML file used in Read customer data and write to a TEMP-TABLE example).

i-sax3d.p

i-sax3d.p is the driver procedure.

/* i-sax3d.p -- SAX-READER progressive scan example.
   Reads customer data from i-sax2.xml one record at a time.
   Each complete Customer record is processed immediately after
   the callback procedures store it, without waiting for the
   entire document to finish parsing. */

DEFINE VARIABLE hParser   AS HANDLE    NO-UNDO.
DEFINE VARIABLE hHandler  AS HANDLE    NO-UNDO.
DEFINE VARIABLE cName     AS CHARACTER NO-UNDO.
DEFINE VARIABLE cPhone    AS CHARACTER NO-UNDO.
DEFINE VARIABLE lComplete AS LOGICAL   NO-UNDO.

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

/* Run the persistent procedure that contains the callback procedures. */
RUN "i-sax3h.p" PERSISTENT SET hHandler.

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

/* Specify the input XML file. */
hParser:SET-INPUT-SOURCE("FILE", "i-sax2.xml").

/* Initiate progressive scan. SAX-PARSE-FIRST() processes the first
   XML token and invokes the corresponding callback, then returns
   control to this procedure. */
hParser:SAX-PARSE-FIRST() NO-ERROR.

IF ERROR-STATUS:ERROR THEN DO:
  MESSAGE "Could not start parse: " + ERROR-STATUS:GET-MESSAGE(1)
    VIEW-AS ALERT-BOX ERROR.
  DELETE OBJECT hParser.
  DELETE OBJECT hHandler.
  STOP.
END.

/* Process one XML token per iteration. After each call to SAX-PARSE-NEXT(),
   control returns here so the business logic can inspect data stored
   by the most recent callback before advancing to the next token. */
DO WHILE hParser:PARSE-STATUS = SAX-RUNNING:

  /* Check whether the callbacks have assembled a complete Customer record. */
  RUN isCustomerComplete IN hHandler (OUTPUT lComplete).

  IF lComplete THEN DO:
    /* Retrieve the stored values and process the completed record. */
    RUN getCustomerData IN hHandler (OUTPUT cName, OUTPUT cPhone).
    DISPLAY
      cName  LABEL "Name"  FORMAT "x(30)"
      cPhone LABEL "Phone" FORMAT "x(14)"
      WITH FRAME fDisplay DOWN.

    /* Reset the handler so it is ready for the next Customer element. */
    RUN resetCustomer IN hHandler.
  END.

  /* Parse the next XML token. The corresponding callback fires
     inside this call and stores any new data before returning. */
  hParser:SAX-PARSE-NEXT() NO-ERROR.

  IF ERROR-STATUS:ERROR THEN DO:
    MESSAGE "Parse error: " + ERROR-STATUS:GET-MESSAGE(1)
      VIEW-AS ALERT-BOX ERROR.
    LEAVE.
  END.
END.

IF hParser:PARSE-STATUS <> SAX-COMPLETE THEN
  MESSAGE "Parse did not complete. PARSE-STATUS = " +
          STRING(hParser:PARSE-STATUS)
    VIEW-AS ALERT-BOX WARNING.

/* Clean up. */
DELETE OBJECT hParser.
DELETE OBJECT hHandler.

i-sax3h.p

i-sax3h.p is the persistent handler procedure. Each callback stores incoming values into module-level variables rather than acting on them directly. The driver accesses completed records through the isCustomerComplete, getCustomerData, and resetCustomer entry points.

/* i-sax3h.p -- Persistent SAX callback handler for i-sax3d.p.
   Callbacks store element data into module-level variables.
   The driver retrieves completed records by calling the
   entry points below. */

DEFINE VARIABLE cCurrentElement AS CHARACTER NO-UNDO.
DEFINE VARIABLE cStoredName     AS CHARACTER NO-UNDO.
DEFINE VARIABLE cStoredPhone    AS CHARACTER NO-UNDO INITIAL "".
DEFINE VARIABLE lRecordReady    AS LOGICAL   NO-UNDO INITIAL FALSE.

/*------------------------------------------------------------------------
  SAX callback procedures
------------------------------------------------------------------------*/

/* Invoked when the parser encounters an opening tag. */
PROCEDURE startElement:
  DEFINE INPUT PARAMETER cNamespaceURI AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER cLocalName    AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER cQName        AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER hAttr         AS HANDLE    NO-UNDO.

  ASSIGN cCurrentElement = cQName.

  /* When a Customer element opens, read the Name attribute and
     reset the stored values for this record. */
  IF cQName = "Customer" THEN
    ASSIGN cStoredName  = hAttr:GET-VALUE-BY-QNAME("Name")
           cStoredPhone = ""
           lRecordReady = FALSE.
END PROCEDURE.

/* Invoked when the parser encounters character data (text content). */
PROCEDURE characters:
  DEFINE INPUT PARAMETER cCharData AS LONGCHAR  NO-UNDO.
  DEFINE INPUT PARAMETER iLength   AS INTEGER   NO-UNDO.

  /* Accumulate phone data; characters() may be called more than
     once for the same element. */
  IF cCurrentElement = "Phone" THEN
    ASSIGN cStoredPhone = cStoredPhone + STRING(cCharData).
END PROCEDURE.

/* Invoked when the parser encounters a closing tag. */
PROCEDURE endElement:
  DEFINE INPUT PARAMETER cNamespaceURI AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER cLocalName    AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER cQName        AS CHARACTER NO-UNDO.

  /* When a Customer element closes, the record is complete.
     Signal the driver by setting lRecordReady. */
  IF cQName = "Customer" THEN
    ASSIGN lRecordReady = TRUE.

  ASSIGN cCurrentElement = "".
END PROCEDURE.

/*------------------------------------------------------------------------
  Entry points for the driver procedure
------------------------------------------------------------------------*/

/* Returns TRUE when a complete Customer record is ready to retrieve. */
PROCEDURE isCustomerComplete:
  DEFINE OUTPUT PARAMETER lDone AS LOGICAL NO-UNDO.
  ASSIGN lDone = lRecordReady.
END PROCEDURE.

/* Returns the name and phone number stored for the current customer. */
PROCEDURE getCustomerData:
  DEFINE OUTPUT PARAMETER cName  AS CHARACTER NO-UNDO.
  DEFINE OUTPUT PARAMETER cPhone AS CHARACTER NO-UNDO.
  ASSIGN cName  = cStoredName
         cPhone = TRIM(cStoredPhone).
END PROCEDURE.

/* Resets stored values so the handler is ready for the next Customer. */
PROCEDURE resetCustomer:
  ASSIGN cStoredName  = ""
         cStoredPhone = ""
         lRecordReady = FALSE.
END PROCEDURE.
Note: This example omits DTD/schema validation and transaction scoping for clarity. For error handling approaches, see Error handling. For background on PARSE-STATUS values (SAX-RUNNING, SAX-COMPLETE, SAX-PARSER-ERROR), see Monitor the state of the parse.