XML GENERATE and PARSE

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an upcoming 4th edition is in ballot), which defines the XML GENERATE and XML PARSE statements (Enterprise COBOL implements them with its own parsers and options); against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2, where XML PARSE runs on the z/OS XML System Services parser or on a parser built into the COBOL library; and against GnuCOBOL 3.2 (28 July 2023, the latest release), with which every program on it was compiled with cobc -x -Wall and run on 2026-10-20 (and again with -std=ibm, which printed the same results). The z/OS programs and job fragments are labelled "z/OS; not run here" and are checked against the Language Reference and the Programming Guide.

XML is the interchange format of older web services and of many enterprise feeds, and the Circulation system uses it to hand the daily loan list to a partner library. XML GENERATE turns a COBOL data item into an XML document, with element names taken from the data names. XML PARSE goes the other way: it reads a document and reports it to the program piece by piece, as a stream of events. The page covers both statements clause by clause, the special registers, the two parsers of Enterprise COBOL, validation against a schema, and what GnuCOBOL 3.2 really does, tested rather than assumed. The sibling page JSON GENERATE and PARSE covers the same job for JSON; the data items being converted are described on Data Division and Level Numbers; the Unicode side (UTF-8, UTF-16 and NATIONAL items) is on National, UTF-8 and Code Pages; and the batch job that would run the export is on JCL for COBOL Programmers.

Where the book is outdated: Taulli’s Modern Mainframe Development (2022) mentions XML only in passing (as an example of semistructured data and in connection with IMS) and does not cover the XML GENERATE and XML PARSE statements, XMLPARSE, or validation, so everything about the statements comes from the official manuals and from running them. Its JSON-first view of services is closer to current practice than XML, which is why this page concentrates on behavior rather than history.

What GnuCOBOL 3.2 supports

The support status was tested on 2026-10-20 in the GnuCOBOL 3.2 container used for this section, which reports XML library : libxml2 in cobc --info.

Feature Enterprise COBOL 6.5 GnuCOBOL 3.2 (tested)

XML GENERATE ... FROM, COUNT IN

Supported.

Works. Compiles with no warning and produces XML.

WITH ATTRIBUTES, NAME OF, TYPE OF, SUPPRESS

Supported.

Work, with output differences shown below.

NAMESPACE, NAMESPACE-PREFIX

Supported.

Work. Every element gets the prefix, and the namespace is declared on the root element.

WITH XML-DECLARATION

Supported.

Works; the declaration it writes is <?xml version="1.0"?>, with no encoding attribute.

WITH ENCODING

Supported.

Compiles with the warning XML GENERATE ENCODING is not implemented; the clause is ignored.

ON EXCEPTION together with NOT ON EXCEPTION

Supported.

Neither branch ran in the test below. Use only ON EXCEPTION, or test XML-CODE.

XML PARSE

Supported with two parsers.

Compiles with the warning XML PARSE is not implemented. At run time the processing procedure sees only two events and nothing is parsed.

VALIDATING WITH, RETURNING NATIONAL

Supported with XMLPARSE(XMLSS).

Not available.

A consequence follows for the rest of the page: the XML GENERATE programs run here and show real output, the XML PARSE programs are written for Enterprise COBOL and are not run, and the portable way to read XML in GnuCOBOL 3.2 is a scanner written in COBOL, shown near the end.

XML GENERATE

The statement converts the data item named after FROM into an XML document in the receiving item.

XML GENERATE receiver FROM source
    [COUNT IN count-field]
    [WITH ENCODING ccsid]
    [WITH XML-DECLARATION]
    [WITH ATTRIBUTES]
    [NAMESPACE IS namespace [NAMESPACE-PREFIX IS prefix]]
    [NAME OF item IS "name" ...]
    [TYPE OF item IS ATTRIBUTE | ELEMENT | CONTENT ...]
    [SUPPRESS item ... WHEN condition | generic-suppression ...]
    [ON EXCEPTION imperative-statement]
    [NOT ON EXCEPTION imperative-statement]
END-XML
  • The receiver is an alphanumeric or national item that is not JUSTIFIED and must be large enough for the whole document. The manual advises a receiver five to ten times the size of the source, because the tags are as long as the data names. A receiver that is too small raises an exception.

  • The source is a group or an elementary item. Items with REDEFINES or RENAMES, unnamed items and FILLER are ignored, and sibling data names must be unique. Elementary items must be alphabetic, alphanumeric, numeric, national or index.

  • COUNT IN receives the number of characters (bytes for an alphanumeric receiver, UTF-16 units for a national one) that were generated. It is the length to use when reading the result back with reference modification, because the rest of the receiver is not cleared.

  • WITH ENCODING chooses the code page of the document by CCSID: 1208 for UTF-8 or a supported EBCDIC page for an alphanumeric receiver, 1200 for a national one. Without it the default follows the receiver type and the compiler options.

  • WITH XML-DECLARATION adds the declaration with the version and encoding. Without the phrase none is written.

  • WITH ATTRIBUTES writes the eligible elementary items (named, not FILLER, no OCCURS) as attributes of their parent element instead of child elements. A TYPE OF phrase overrides it for single items.

  • NAMESPACE and NAMESPACE-PREFIX name the namespace of the generated names. Without a prefix the namespace becomes the default namespace; with one, every tag carries the prefix.

  • NAME OF replaces the generated name of an element or attribute, which must be a valid XML local name. When an item is listed more than once the last entry wins.

  • TYPE OF makes one elementary item an ATTRIBUTE, an ELEMENT, or CONTENT; content of several items under one parent is concatenated.

  • SUPPRESS leaves items out, either one at a time with a WHEN condition (ZERO, SPACE, LOW-VALUE, HIGH-VALUE) or generically by kind. The root element is always generated, and a group left empty is dropped.

  • ON EXCEPTION runs when generation fails; XML-CODE then holds the code. After a successful statement XML-CODE is zero. END-XML ends the statement, which is required when it is nested inside another conditional statement.

The same record four ways

The first program reads the first book of the BOOKMAST file through the BOOKREC copybook and generates XML four times, to show the effect of the phrases. The copybook and data are those of Copybooks and COPY REPLACING.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. XMLGEN1.
      * XML GENERATE: one book record, four ways.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT BOOK-FILE ASSIGN TO "BOOKMAST"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  BOOK-FILE.
           COPY BOOKREC.
       WORKING-STORAGE SECTION.
       01  XML-BUF                 PIC X(400).
       01  XML-LEN                 PIC 9(4).
       PROCEDURE DIVISION.
       MAIN-PARA.
           OPEN INPUT BOOK-FILE
           READ BOOK-FILE
           CLOSE BOOK-FILE

           XML GENERATE XML-BUF FROM BOOK-RECORD
               COUNT IN XML-LEN
               ON EXCEPTION
                   DISPLAY "FAILED, XML-CODE " XML-CODE
           END-XML
           DISPLAY "1 elements:   " XML-BUF(1:XML-LEN)

           XML GENERATE XML-BUF FROM BOOK-RECORD
               COUNT IN XML-LEN
               WITH ATTRIBUTES
               ON EXCEPTION
                   DISPLAY "FAILED, XML-CODE " XML-CODE
           END-XML
           DISPLAY "2 attributes: " XML-BUF(1:XML-LEN)

           XML GENERATE XML-BUF FROM BOOK-RECORD
               COUNT IN XML-LEN
               NAME OF BOOK-RECORD IS "book"
                       BOOK-ID IS "id"
                       BOOK-TITLE IS "title"
               TYPE OF BOOK-ID IS ATTRIBUTE
               SUPPRESS BOOK-ISBN BOOK-PRICE BOOK-STATUS
               ON EXCEPTION
                   DISPLAY "FAILED, XML-CODE " XML-CODE
           END-XML
           DISPLAY "3 names:      " XML-BUF(1:XML-LEN)

           XML GENERATE XML-BUF FROM BOOK-RECORD
               COUNT IN XML-LEN
               NAMESPACE IS "urn:example:circulation"
               NAMESPACE-PREFIX IS "c"
               SUPPRESS BOOK-ISBN BOOK-PRICE BOOK-STATUS
               ON EXCEPTION
                   DISPLAY "FAILED, XML-CODE " XML-CODE
           END-XML
           DISPLAY "4 namespace:  " XML-BUF(1:XML-LEN)
           STOP RUN.

Compiled with cobc -x -Wall -I. xmlgen1.cob (and with -std=ibm), no warnings, it prints (the long lines are not wrapped):

1 elements:   <BOOK-RECORD><BOOK-ID>1</BOOK-ID><BOOK-ISBN>9780000000011</BOOK-ISBN><BOOK-TITLE>DATA STRUCTURES</BOOK-TITLE><BOOK-COPIES>3</BOOK-COPIES><BOOK-PRICE>24.50</BOOK-PRICE><BOOK-STATUS>A</BOOK-STATUS></BOOK-RECORD>
2 attributes: <BOOK-RECORD BOOK-ID="000001" BOOK-ISBN="9780000000011" BOOK-TITLE="DATA STRUCTURES" BOOK-COPIES="003" BOOK-PRICE="002450" BOOK-STATUS="A"/>
3 names:      <book id="000001"><title>DATA STRUCTURES</title><BOOK-COPIES>3</BOOK-COPIES></book>
4 namespace:  <c:BOOK-RECORD xmlns:c="urn:example:circulation"><c:BOOK-ID>1</c:BOOK-ID><c:BOOK-TITLE>DATA STRUCTURES</c:BOOK-TITLE><c:BOOK-COPIES>3</c:BOOK-COPIES></c:BOOK-RECORD>

What the output shows, as GnuCOBOL 3.2 behaves:

  • Names come from the data names. The root element is named after the source group and the children after the fields, in upper case, as in the source. The trailing FILLER is not generated.

  • Elements and attributes format numbers differently. In an element, BOOK-ID (PIC 9(6), value 1) is 1 and BOOK-PRICE (PIC 9(4)V99) is 24.50: leading zeros are dropped and the decimal point is inserted. In an attribute the same fields are 000001 and 002450: the raw digits, with no decimal point. A price written as an attribute is therefore wrong by a factor of 100 for a reader that expects a decimal number. Keep decimals in elements, or move them to a numeric-edited item first. The exact text Enterprise COBOL produces for each numeric category is in the Programming Guide and was not run here, so compare the output on the target platform instead of assuming this behavior carries over.

  • Alphanumeric data lose trailing spaces. BOOK-TITLE is DATA STRUCTURES without the padding to 30 characters.

  • SUPPRESS with a list of names removed BOOK-ISBN, BOOK-PRICE and BOOK-STATUS in the third and fourth documents, which is how a few fields of a wide record are exported.

  • NAME OF and TYPE OF renamed the root to book, the id to id, moved it to an attribute and renamed the title.

  • The namespace prefix is applied to every tag and the namespace is declared once, on the root.

A numeric field whose value is wanted exactly as stored, with the zeros, must be an alphanumeric item or an edited field; this is the same decision that JSON GENERATE forces.

Options and diagnostics

Two further options were tested on a one-record group. WITH XML-DECLARATION produced a first line <?xml version="1.0"?> and then a line feed (the single byte X'0A', confirmed with od -c) before the root element, so the generated text is two lines long. WITH ENCODING 1208 is accepted but ignored, and the compiler says so:

h7.cob:17: warning: XML GENERATE ENCODING is not implemented [-Wpending]
   15 |                COUNT IN OUT-LEN
   16 |                ENCODING 1208
   17 >            END-XML

A program that depends on a given encoding must therefore not rely on GnuCOBOL 3.2 to set it. The generated bytes are the bytes of the receiver, whatever the platform uses for characters; this matters on z/OS, where the receiver is EBCDIC unless WITH ENCODING 1208 asks for UTF-8.

A receiver that is too small fails, and XML-CODE says why. In the following test the receiver was 20 bytes for a document of 82:

FAILED, XML-CODE +000000400
COUNT 0082

Code 400 is the code that the Enterprise COBOL manual documents for "the receiver is too small", and GnuCOBOL 3.2 sets the same value. The count was the size the document needed in GnuCOBOL; Enterprise COBOL documents the count as the number of characters generated. The other documented generation codes are 401 and 402 (invalid characters in a DBCS name), 403 (an OCCURS DEPENDING ON object larger than 16,777,215), 410 to 415 (code page problems), 416 and 419 (invalid namespace or prefix), 417 (illegal characters in the data, which are generated as hexadecimal under a hex. name), 418 (substitution characters from the conversion), 420 (national data in an alphanumeric receiver that is not UTF-8) and 600 to 699 (internal errors).

One more finding from the tests concerns the exception phrases. A statement that has ON EXCEPTION and NOT ON EXCEPTION together compiled with no warning, but neither branch ran when the generation succeeded:

           XML GENERATE OUT-XML FROM BOOK-OUT
               COUNT IN OUT-LEN
               ON EXCEPTION
                   DISPLAY "FAILED " XML-CODE
               NOT ON EXCEPTION
                   DISPLAY "OK"
           END-XML
           DISPLAY "after"
after

With only ON EXCEPTION, or only NOT ON EXCEPTION, in separate statements, each phrase worked. In GnuCOBOL 3.2 use ON EXCEPTION alone and test XML-CODE after the statement; it is the portable form, and it is also valid in Enterprise COBOL.

XML PARSE and the processing procedure

XML PARSE does not build a data structure. It reads the document and calls a processing procedure for every piece it recognizes, called an event. The procedure inspects the special registers, moves what it needs into data items, and returns; the parser then continues from where it stopped.

XML PARSE document
    [WITH ENCODING ccsid]
    [RETURNING NATIONAL]
    [VALIDATING WITH osr-item | VALIDATING WITH FILE schema-name]
    PROCESSING PROCEDURE IS first-procedure [THROUGH last-procedure]
    [ON EXCEPTION imperative-statement]
    [NOT ON EXCEPTION imperative-statement]
END-XML
  • The document is an alphanumeric or national item, elementary or group (not a function result or a dynamic-length group). National text must be UTF-16 big-endian (CCSID 1200); alphanumeric text must be in a supported character set.

  • WITH ENCODING, RETURNING NATIONAL and VALIDATING need XMLPARSE(XMLSS). WITH ENCODING overrides the CODEPAGE option and any encoding declaration in the document. RETURNING NATIONAL makes an alphanumeric document deliver its fragments as UTF-16 in the national registers.

  • PROCESSING PROCEDURE names a section or a paragraph range in the Procedure Division. The procedure must not run another XML PARSE (unless it is in a called program) and must not issue GOBACK or EXIT PROGRAM to leave the parse, though STOP RUN is allowed. When the document ends, control returns to the statement after the XML PARSE.

  • ON EXCEPTION and NOT ON EXCEPTION. The parser first sends an EXCEPTION event to the processing procedure with the error number in XML-CODE; when parsing ends with an exception, ON EXCEPTION runs, otherwise NOT ON EXCEPTION. A procedure that sets XML-CODE to zero for an error it handled sends control to the NOT ON EXCEPTION branch. Without ON EXCEPTION, any NOT ON EXCEPTION phrase is ignored.

The special registers

Register Contents

XML-EVENT

The name of the current event, as a character string such as START-OF-ELEMENT. Compare it with literals; do not use it as a receiving item.

XML-TEXT

A variable-length alphanumeric item with the document fragment of the event, for an alphanumeric document read without RETURNING NATIONAL. Read-only.

XML-NTEXT

The same fragment as national data, for a national document, with RETURNING NATIONAL, and for numeric character references.

XML-CODE

Zero after a successful parse. During the procedure it carries the exception code on an EXCEPTION event. Setting it to -1 in the procedure stops the parse without an EXCEPTION event.

XML-NAMESPACE, XML-NNAMESPACE, XML-NAMESPACE-PREFIX, XML-NNAMESPACE-PREFIX

With XMLPARSE(XMLSS), the namespace identifier and prefix of the element or attribute of the current event.

XML-INFORMATION

Used to parse a large document one segment at a time.

The events

The Language Reference lists the events. The most used are in the table; the text of a fragment may arrive in several pieces under XMLPARSE(XMLSS), so a program accumulates content until the end of the element.

XML-EVENT Meaning XML-TEXT

START-OF-DOCUMENT

The start of the document.

Empty with XMLSS; the whole document with COMPAT.

VERSION-INFORMATION, ENCODING-DECLARATION, STANDALONE-DECLARATION

The parts of the XML declaration.

The value, without quotes.

START-OF-ELEMENT

A start tag or an empty-element tag.

With XMLSS, the local name (the namespace and prefix are in the namespace registers); with COMPAT, the full tag name.

ATTRIBUTE-NAME, ATTRIBUTE-CHARACTERS

An attribute name, and the text of its value.

The name, and the value inside the quotes (possibly a piece of it).

CONTENT-CHARACTERS

Text between the start and end tags.

The text, possibly in pieces.

END-OF-ELEMENT

An end tag or an empty-element tag.

The local name with XMLSS, the full name with COMPAT.

COMMENT, PROCESSING-INSTRUCTION-TARGET, PROCESSING-INSTRUCTION-DATA

Comments and processing instructions.

Their text.

START-OF-CDATA-SECTION, END-OF-CDATA-SECTION

The delimiters of a CDATA section.

Empty with XMLSS; the delimiter text with COMPAT.

NAMESPACE-DECLARATION (XMLSS only)

A namespace declaration.

Empty; the namespace registers hold the declaration.

UNRESOLVED-REFERENCE (XMLSS only)

An entity reference that cannot be resolved.

The entity name.

END-OF-INPUT (XMLSS only)

The parser needs more input.

Empty. To go on, move the next segment into the document item and set XML-CODE to 1.

END-OF-DOCUMENT

The end of the document.

Empty.

EXCEPTION

A well-formedness, encoding or validation error.

The fragment up to the error; XML-CODE holds the code.

COMPAT also reports ATTRIBUTE-CHARACTER, CONTENT-CHARACTER and the UNKNOWN-REFERENCE-IN-... events for predefined and unknown entity references, where XMLSS delivers them inside the character events or as an EXCEPTION. National character references are reported as ATTRIBUTE-NATIONAL-CHARACTER and CONTENT-NATIONAL-CHARACTER, with the character in XML-NTEXT and XML-TEXT empty.

The two parsers: XMLPARSE(XMLSS) and COMPAT

The XMLPARSE compiler option chooses the parser and its default is XMLSS.

  • XMLPARSE(XMLSS) uses the z/OS XML System Services parser. It is the only one that supports VALIDATING, WITH ENCODING, RETURNING NATIONAL, the namespace registers, direct parsing of UTF-8, parsing a buffer at a time and, where the hardware allows it, offloading the work to a specialty processor. A program that uses XML PARSE must be link-edited with AMODE 31.

  • XMLPARSE(COMPAT) uses the parser inside the COBOL library and behaves as the older compilers did. It has none of the advanced features above; its events carry full tag names and, for START-OF-DOCUMENT, the whole document.

Choose XMLSS for new code and COMPAT only for a program that must keep its old results.

A processing procedure for the Circulation loans (z/OS; not run here)

The program reads a loan document into the fields of LOAN-RECORD. It is written for XMLPARSE(XMLSS) and checked against the syntax in the Language Reference; it cannot be run in GnuCOBOL 3.2. It remembers the current element name at each START-OF-ELEMENT, moves the text of CONTENT-CHARACTERS to the matching field, and treats EXCEPTION as a rejected document.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. LOANXML.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
           COPY LOANREC.
       01  LOAN-DOC            PIC X(200).
       01  CUR-ELEMENT         PIC X(10).
       PROCEDURE DIVISION.
       MAIN-PARA.
           STRING "<loan><type>O</type><member>000100</member>"
                  "<book>000001</book><date>20261020</date></loan>"
               DELIMITED BY SIZE INTO LOAN-DOC
           XML PARSE LOAN-DOC
               PROCESSING PROCEDURE READ-LOAN
               ON EXCEPTION
                   DISPLAY "REJECTED, XML-CODE " XML-CODE
               NOT ON EXCEPTION
                   DISPLAY "LOAN " LOAN-MEMBER-ID " " LOAN-BOOK-ID
           END-XML
           GOBACK.

       READ-LOAN SECTION.
           EVALUATE XML-EVENT
               WHEN "START-OF-ELEMENT"
                   MOVE XML-TEXT TO CUR-ELEMENT
               WHEN "CONTENT-CHARACTERS"
                   EVALUATE CUR-ELEMENT
                       WHEN "type"
                           MOVE XML-TEXT TO LOAN-TYPE
                       WHEN "member"
                           MOVE XML-TEXT TO LOAN-MEMBER-ID
                       WHEN "book"
                           MOVE XML-TEXT TO LOAN-BOOK-ID
                       WHEN "date"
                           MOVE XML-TEXT TO LOAN-DATE
                   END-EVALUATE
               WHEN "EXCEPTION"
                   DISPLAY "PARSE ERROR NEAR: " XML-TEXT
           END-EVALUATE.

Points to note when adapting it:

  • XML-TEXT has a variable length. Moving it to a numeric field is only meaningful when it holds digits, so a program that reads untrusted input should test it first, for example with IF XML-TEXT IS NUMERIC, and reject the document otherwise.

  • Content may arrive in pieces. A short value is normally delivered whole, but a long one, or one that contains entity references such as the ampersand, may be split. For free text, append each piece to a work field with STRING and act at END-OF-ELEMENT.

  • The exceptions. A malformed document (for example a start tag that is never closed) sends an EXCEPTION event, sets XML-CODE and, at the end of the statement, runs ON EXCEPTION. The reason codes are in the Programming Guide for each parser, and a program that reports them should print the code in hexadecimal.

  • Stopping early. To stop after the data needed has been read, set XML-CODE to -1 in the procedure; the parse ends without an EXCEPTION event.

Validation against an OSR

With XMLPARSE(XMLSS), XML PARSE can check each document against an XML schema. The parser does not read the schema text. It needs the schema in a compiled binary form called an Optimized Schema Representation (OSR), which is produced from the .xsd file by the z/OS UNIX command xsdosrg. In outline, for a Circulation loan schema (z/OS; not run here):

xsdosrg -v -o /u/circ/xml/loan.osr /u/circ/xml/loan.xsd

The OSR is then made available to the program in one of two ways.

  1. By file. A SPECIAL-NAMES entry maps a schema name to a ddname, the ddname is pointed at the OSR when the program runs, and XML PARSE names the schema with VALIDATING WITH FILE. This form is not supported under CICS.

  2. By data item. The program reads the OSR into an alphanumeric item with ordinary file statements and names the item in VALIDATING WITH. The schema is read once and reused, which is faster when many documents of the same type are validated.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       SPECIAL-NAMES.
           XML-SCHEMA LOAN-SCHEMA IS "DDSCHEMA".
      * ...
           XML PARSE LOAN-DOC
               VALIDATING WITH FILE LOAN-SCHEMA
               PROCESSING PROCEDURE READ-LOAN
               ON EXCEPTION
                   DISPLAY "INVALID, XML-CODE " XML-CODE
               NOT ON EXCEPTION
                   DISPLAY "VALID"
           END-XML
//GO.DDSCHEMA DD PATH='/u/circ/xml/loan.osr'

A document that is well formed but does not match the schema produces an EXCEPTION event. XML-CODE then holds the return code 24 (hexadecimal 18, the "not valid" return code) in the high-order halfword and a reason code in the low-order halfword, so the program can distinguish a validation failure from a syntax error by the high halfword. Without VALIDATING, the parser checks only that the document is well formed. The compile option must be XMLPARSE(XMLSS); COMPAT does not validate. The XML-SCHEMA clause and the exact handling of the reason codes are in the Language Reference and the Programming Guide.

XML PARSE in GnuCOBOL 3.2

The statement is accepted by the compiler but not implemented. This program has a processing procedure that displays each event and text and parses two documents, the second malformed on purpose:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. P1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  DOC PIC X(60) VALUE
           "<loan type='O'><member>000100</member></loan>".
       01  DOC2 PIC X(20) VALUE "<loan><oops></loan>".
       PROCEDURE DIVISION.
           XML PARSE DOC PROCESSING PROCEDURE SHOW
               ON EXCEPTION DISPLAY "EXC " XML-CODE
               NOT ON EXCEPTION DISPLAY "OK " XML-CODE
           END-XML
           XML PARSE DOC2 PROCESSING PROCEDURE SHOW
               ON EXCEPTION DISPLAY "EXC " XML-CODE
               NOT ON EXCEPTION DISPLAY "OK " XML-CODE
           END-XML
           STOP RUN.
       SHOW SECTION.
           DISPLAY XML-EVENT " [" XML-TEXT "]".

Compiled with cobc -x -Wall p1.cob, it gives a warning for each XML PARSE:

p1.cob:9: warning: XML PARSE is not implemented [-Wpending]
    7 |        01  DOC2 PIC X(20) VALUE "<loan><oops></loan>".
    8 |        PROCEDURE DIVISION.
    9 >            XML PARSE DOC PROCESSING PROCEDURE SHOW
   10 |                ON EXCEPTION DISPLAY "EXC " XML-CODE
   11 |                NOT ON EXCEPTION DISPLAY "OK " XML-CODE

and at run time the processing procedure sees the same two events for both documents:

START-OF-DOCUMENT []
END-OF-INPUT []
START-OF-DOCUMENT []
END-OF-INPUT []

Neither document is read, the malformed one is not detected, and neither ON EXCEPTION nor NOT ON EXCEPTION ran. A program that ports an XML PARSE job to GnuCOBOL 3.2 would therefore compile and then process nothing without any error, which is why the warning must be treated as an error in a build (-Werror, or a check of the compiler output).

A portable scanner for GnuCOBOL

When the XML is simple and under the program’s own control, as the loan list of the Circulation system is, a short scanner is enough. The following program reads the first two transactions of LOANTRAN, makes a document with XML GENERATE and NAME OF, and then walks the text with reference modification, emitting the same kind of events as XML PARSE and filling a work record in the handler. It is written for a well-formed document of elements and text; it does not handle attributes, entities, comments, CDATA or namespaces, and it does not detect malformed XML. It is a stand-in for the parser, not a parser.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. XMLSCAN.
      * Round trip: XML GENERATE the loans, then walk the text with a
      * hand-written event scanner (GnuCOBOL 3.2 has no XML PARSE).
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT LOAN-FILE ASSIGN TO "LOANTRAN"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  LOAN-FILE.
           COPY LOANREC.
       WORKING-STORAGE SECTION.
       01  EOF-FLAG                PIC X       VALUE "N".
           88  AT-END              VALUE "Y".
       01  ONE-XML                 PIC X(200).
       01  ONE-LEN                 PIC 9(4).
       01  DOC                     PIC X(800).
       01  DOC-LEN                 PIC 9(4)    VALUE 0.
       01  POS                     PIC 9(4).
       01  TAG-END                 PIC 9(4).
       01  TXT-END                 PIC 9(4).
       01  EVENT-NAME              PIC X(20).
       01  EVENT-TEXT              PIC X(30).
       01  CURRENT-TAG             PIC X(20).
       01  LOAN-COUNT              PIC 9(2)    VALUE 0.
       01  READ-COUNT              PIC 9(2)    VALUE 0.
       01  NEW-LOAN.
           05  NL-TYPE             PIC X.
           05  NL-MEMBER           PIC X(6).
           05  NL-BOOK             PIC X(6).
       PROCEDURE DIVISION.
       MAIN-PARA.
           OPEN INPUT LOAN-FILE
           MOVE "<loans>" TO DOC
           MOVE 7 TO DOC-LEN
           PERFORM UNTIL AT-END OR READ-COUNT = 2
               READ LOAN-FILE
                   AT END SET AT-END TO TRUE
                   NOT AT END
                       ADD 1 TO READ-COUNT
                       PERFORM ADD-LOAN
               END-READ
           END-PERFORM
           CLOSE LOAN-FILE
           MOVE "</loans>" TO DOC(DOC-LEN + 1:8)
           ADD 8 TO DOC-LEN
           DISPLAY DOC(1:DOC-LEN)
           PERFORM SCAN-DOC
           STOP RUN.

       ADD-LOAN.
           XML GENERATE ONE-XML FROM LOAN-RECORD
               COUNT IN ONE-LEN
               NAME OF LOAN-RECORD IS "loan"
                       LOAN-TYPE IS "type"
                       LOAN-MEMBER-ID IS "member"
                       LOAN-BOOK-ID IS "book"
                       LOAN-DATE IS "date"
               ON EXCEPTION
                   DISPLAY "FAILED, XML-CODE " XML-CODE
           END-XML
           MOVE ONE-XML(1:ONE-LEN) TO DOC(DOC-LEN + 1:ONE-LEN)
           ADD ONE-LEN TO DOC-LEN.

       SCAN-DOC.
           MOVE 1 TO POS
           PERFORM UNTIL POS > DOC-LEN
               IF DOC(POS:1) = "<"
                   MOVE POS TO TAG-END
                   PERFORM UNTIL DOC(TAG-END:1) = ">"
                       ADD 1 TO TAG-END
                   END-PERFORM
                   IF DOC(POS + 1:1) = "/"
                       MOVE "END-OF-ELEMENT" TO EVENT-NAME
                       MOVE DOC(POS + 2:TAG-END - POS - 2)
                           TO EVENT-TEXT
                   ELSE
                       MOVE "START-OF-ELEMENT" TO EVENT-NAME
                       MOVE DOC(POS + 1:TAG-END - POS - 1)
                           TO EVENT-TEXT
                       MOVE EVENT-TEXT TO CURRENT-TAG
                   END-IF
                   COMPUTE POS = TAG-END + 1
               ELSE
                   MOVE POS TO TXT-END
                   PERFORM UNTIL DOC(TXT-END + 1:1) = "<"
                       ADD 1 TO TXT-END
                   END-PERFORM
                   MOVE "CONTENT-CHARACTERS" TO EVENT-NAME
                   MOVE DOC(POS:TXT-END - POS + 1) TO EVENT-TEXT
                   COMPUTE POS = TXT-END + 1
               END-IF
               PERFORM HANDLE-EVENT
           END-PERFORM.

       HANDLE-EVENT.
           DISPLAY EVENT-NAME " [" FUNCTION TRIM(EVENT-TEXT) "]"
           EVALUATE EVENT-NAME ALSO CURRENT-TAG
               WHEN "CONTENT-CHARACTERS" ALSO "type"
                   MOVE EVENT-TEXT TO NL-TYPE
               WHEN "CONTENT-CHARACTERS" ALSO "member"
                   MOVE EVENT-TEXT TO NL-MEMBER
               WHEN "CONTENT-CHARACTERS" ALSO "book"
                   MOVE EVENT-TEXT TO NL-BOOK
               WHEN "END-OF-ELEMENT" ALSO ANY
                   IF EVENT-TEXT = "loan"
                       ADD 1 TO LOAN-COUNT
                       DISPLAY "  loan " LOAN-COUNT ": " NL-TYPE
                           " member " FUNCTION TRIM(NL-MEMBER)
                           " book " FUNCTION TRIM(NL-BOOK)
                   END-IF
           END-EVALUATE.

Compiled with cobc -x -Wall -I. xmlscan.cob (and with -std=ibm), no warnings, it prints:

<loans><loan><type>O</type><member>100</member><book>1</book><date>20261020</date></loan><loan><type>O</type><member>101</member><book>2</book><date>20261020</date></loan></loans>
START-OF-ELEMENT     [loans]
START-OF-ELEMENT     [loan]
START-OF-ELEMENT     [type]
CONTENT-CHARACTERS   [O]
END-OF-ELEMENT       [type]
START-OF-ELEMENT     [member]
CONTENT-CHARACTERS   [100]
END-OF-ELEMENT       [member]
START-OF-ELEMENT     [book]
CONTENT-CHARACTERS   [1]
END-OF-ELEMENT       [book]
START-OF-ELEMENT     [date]
CONTENT-CHARACTERS   [20261020]
END-OF-ELEMENT       [date]
END-OF-ELEMENT       [loan]
  loan 01: O member 100 book 1
START-OF-ELEMENT     [loan]
START-OF-ELEMENT     [type]
CONTENT-CHARACTERS   [O]
END-OF-ELEMENT       [type]
START-OF-ELEMENT     [member]
CONTENT-CHARACTERS   [101]
END-OF-ELEMENT       [member]
START-OF-ELEMENT     [book]
CONTENT-CHARACTERS   [2]
END-OF-ELEMENT       [book]
START-OF-ELEMENT     [date]
CONTENT-CHARACTERS   [20261020]
END-OF-ELEMENT       [date]
END-OF-ELEMENT       [loan]
  loan 02: O member 101 book 2
END-OF-ELEMENT       [loans]
  • The generated ids have lost their leading zeros (100, not 000100), as in the element column of the earlier table; a consumer that expects six digits must pad them, for example by moving it to a PIC 9(6) field, or the generator must write them from an alphanumeric item.

  • The event names are those of XML PARSE, so the handler paragraph can be moved into a real processing procedure on z/OS with its EVALUATE mostly unchanged, with XML-EVENT and XML-TEXT instead of the work items.

  • A real document needs a real parser. For large or untrusted input from GnuCOBOL, call a library such as libxml2 from C (see Interoperability with C, Java and Python) or convert the XML to fixed-format records before the COBOL step.

Common mistakes

  • Reading the whole receiver. XML GENERATE does not clear the rest of the receiver. Use the COUNT IN length, as the examples do, or the document ends in leftover bytes.

  • A receiver that is too small. The tags are as long as the data names, so the document is several times larger than the record. Code 400 is the result; size the receiver generously and always code ON EXCEPTION.

  • Decimals in attributes. In GnuCOBOL 3.2 a PIC 9(4)V99 field written as an attribute loses its decimal point (002450 for 24.50). Keep it in an element or format it first.

  • Assuming the leading zeros. In GnuCOBOL 3.2 an element value is written without leading zeros. A partner that expects 000100 must receive an alphanumeric field.

  • Both exception phrases in GnuCOBOL 3.2. The test showed neither branch running; use ON EXCEPTION alone and test XML-CODE.

  • Trusting a clean compile of XML PARSE in GnuCOBOL 3.2. It warns and then does not parse. Treat the warning as an error.

  • Using XML-TEXT as one piece. The text of an event can be split under XMLPARSE(XMLSS); accumulate it until END-OF-ELEMENT.

  • Running XML PARSE or GOBACK inside the processing procedure. Both are not allowed there on z/OS; set XML-CODE to -1 to stop early.

  • Validating with COMPAT. Only XMLPARSE(XMLSS) supports VALIDATING.

  • Forgetting the encoding on z/OS. An alphanumeric receiver is EBCDIC unless the statement says WITH ENCODING 1208; a file sent to another platform must be UTF-8 with a matching declaration.

  • Parsing untrusted XML with a hand-written scanner. The scanner above accepts only the simple documents the program wrote itself.