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) |
|---|---|---|
|
Supported. |
Works. Compiles with no warning and produces XML. |
|
Supported. |
Work, with output differences shown below. |
|
Supported. |
Work. Every element gets the prefix, and the namespace is declared on the root element. |
|
Supported. |
Works; the declaration it writes is |
|
Supported. |
Compiles with the warning |
|
Supported. |
Neither branch ran in the test below. Use only |
|
Supported with two parsers. |
Compiles with the warning |
|
Supported with |
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
JUSTIFIEDand 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
REDEFINESorRENAMES, unnamed items andFILLERare ignored, and sibling data names must be unique. Elementary items must be alphabetic, alphanumeric, numeric, national or index. -
COUNT INreceives 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 ENCODINGchooses 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-DECLARATIONadds the declaration with the version and encoding. Without the phrase none is written. -
WITH ATTRIBUTESwrites the eligible elementary items (named, notFILLER, noOCCURS) as attributes of their parent element instead of child elements. ATYPE OFphrase overrides it for single items. -
NAMESPACEandNAMESPACE-PREFIXname the namespace of the generated names. Without a prefix the namespace becomes the default namespace; with one, every tag carries the prefix. -
NAME OFreplaces 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 OFmakes one elementary item anATTRIBUTE, anELEMENT, orCONTENT; content of several items under one parent is concatenated. -
SUPPRESSleaves items out, either one at a time with aWHENcondition (ZERO,SPACE,LOW-VALUE,HIGH-VALUE) or generically by kind. The root element is always generated, and a group left empty is dropped. -
ON EXCEPTIONruns when generation fails;XML-CODEthen holds the code. After a successful statementXML-CODEis zero.END-XMLends 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
FILLERis not generated. -
Elements and attributes format numbers differently. In an element,
BOOK-ID(PIC 9(6), value 1) is1andBOOK-PRICE(PIC 9(4)V99) is24.50: leading zeros are dropped and the decimal point is inserted. In an attribute the same fields are000001and002450: 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-TITLEisDATA STRUCTURESwithout the padding to 30 characters. -
SUPPRESSwith a list of names removedBOOK-ISBN,BOOK-PRICEandBOOK-STATUSin the third and fourth documents, which is how a few fields of a wide record are exported. -
NAME OFandTYPE OFrenamed the root tobook, the id toid, 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 NATIONALandVALIDATINGneedXMLPARSE(XMLSS).WITH ENCODINGoverrides theCODEPAGEoption and any encoding declaration in the document.RETURNING NATIONALmakes an alphanumeric document deliver its fragments as UTF-16 in the national registers. -
PROCESSING PROCEDUREnames a section or a paragraph range in the Procedure Division. The procedure must not run anotherXML PARSE(unless it is in a called program) and must not issueGOBACKorEXIT PROGRAMto leave the parse, thoughSTOP RUNis allowed. When the document ends, control returns to the statement after theXML PARSE. -
ON EXCEPTIONandNOT ON EXCEPTION. The parser first sends anEXCEPTIONevent to the processing procedure with the error number inXML-CODE; when parsing ends with an exception,ON EXCEPTIONruns, otherwiseNOT ON EXCEPTION. A procedure that setsXML-CODEto zero for an error it handled sends control to theNOT ON EXCEPTIONbranch. WithoutON EXCEPTION, anyNOT ON EXCEPTIONphrase is ignored.
The special registers
| Register | Contents |
|---|---|
|
The name of the current event, as a character string such as |
|
A variable-length alphanumeric item with the document fragment of the event, for an alphanumeric document read without |
|
The same fragment as national data, for a national document, with |
|
Zero after a successful parse. During the procedure it carries the exception code on an |
|
With |
|
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 |
|---|---|---|
|
The start of the document. |
Empty with |
|
The parts of the XML declaration. |
The value, without quotes. |
|
A start tag or an empty-element tag. |
With |
|
An attribute name, and the text of its value. |
The name, and the value inside the quotes (possibly a piece of it). |
|
Text between the start and end tags. |
The text, possibly in pieces. |
|
An end tag or an empty-element tag. |
The local name with |
|
Comments and processing instructions. |
Their text. |
|
The delimiters of a CDATA section. |
Empty with |
|
A namespace declaration. |
Empty; the namespace registers hold the declaration. |
|
An entity reference that cannot be resolved. |
The entity name. |
|
The parser needs more input. |
Empty. To go on, move the next segment into the document item and set |
|
The end of the document. |
Empty. |
|
A well-formedness, encoding or validation error. |
The fragment up to the error; |
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 supportsVALIDATING,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 usesXML PARSEmust be link-edited withAMODE 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, forSTART-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-TEXThas 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 withIF 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
STRINGand act atEND-OF-ELEMENT. -
The exceptions. A malformed document (for example a start tag that is never closed) sends an
EXCEPTIONevent, setsXML-CODEand, at the end of the statement, runsON 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-CODEto -1 in the procedure; the parse ends without anEXCEPTIONevent.
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.
-
By file. A
SPECIAL-NAMESentry maps a schema name to a ddname, the ddname is pointed at the OSR when the program runs, andXML PARSEnames the schema withVALIDATING WITH FILE. This form is not supported under CICS. -
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, not000100), as in the element column of the earlier table; a consumer that expects six digits must pad them, for example by moving it to aPIC 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 itsEVALUATEmostly unchanged, withXML-EVENTandXML-TEXTinstead 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 GENERATEdoes not clear the rest of the receiver. Use theCOUNT INlength, 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)V99field written as an attribute loses its decimal point (002450for 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
000100must receive an alphanumeric field. -
Both exception phrases in GnuCOBOL 3.2. The test showed neither branch running; use
ON EXCEPTIONalone and testXML-CODE. -
Trusting a clean compile of
XML PARSEin GnuCOBOL 3.2. It warns and then does not parse. Treat the warning as an error. -
Using
XML-TEXTas one piece. The text of an event can be split underXMLPARSE(XMLSS); accumulate it untilEND-OF-ELEMENT. -
Running
XML PARSEorGOBACKinside the processing procedure. Both are not allowed there on z/OS; setXML-CODEto -1 to stop early. -
Validating with
COMPAT. OnlyXMLPARSE(XMLSS)supportsVALIDATING. -
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.
References
-
Enterprise COBOL for z/OS 6.5 Language Reference: XML GENERATE statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: XML PARSE statement
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Processing XML input
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Parsing XML documents
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Writing procedures to process XML
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Parsing XML documents with validation
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Terminating XML parsing
-
Enterprise COBOL for z/OS 6.5 Programming Guide: XML PARSE exceptions with XMLPARSE(XMLSS)
-
Enterprise COBOL for z/OS 6.5 Programming Guide: XML GENERATE exceptions
-
Enterprise COBOL for z/OS 6.5 Programming Guide: XMLPARSE compiler option
-
Tom Taulli, Modern Mainframe Development, O’Reilly Media, 2022 (consulted for the topics only)