在Enterprise COBOL中,XML PARSE是一条内置的语句,能够以事件驱动模型把XML文档解析成一系列可供COBOL程序处理的片段。它不需要借助外部中间件,直接运行在批处理或联机程序中,非常适合银行、保险等遗留系统对接现代接口的场景。理解它的工作机制,可以让老系统以很低成本消费XML格式的数据。

XML PARSE的基本语法与执行流程
XML PARSE语句最简单的写法是指定一个包含XML文本的变量,并在其后的PROCEDURE DIVISION段落中编写处理例程。解析器在碰到文档开始、每个元素的起始标签、字符数据和结束标签时,都会触发一次处理例程调用,并通过特殊寄存器告诉COBOL当前发生了什么事件。
下面的示例展示了一个最小可用的解析框架。我们把XML文档放在WS-XML-DOC里,用XML PARSE解析,处理例程根据XML-EVENT的值决定动作。注意处理例程必须用段落或节来写,且通过CONTINUE或Fall through回到解析器。
IDENTIFICATION DIVISION.
PROGRAM-ID. DEMOXML.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-XML-DOC PIC X(200) VALUE
'<root><item>Apple</item><item>Banana</item></root>'.
01 WS-ITEM PIC X(20).
PROCEDURE DIVISION.
XML PARSE WS-XML-DOC
PROCESSING PROCEDURE XML-HANDLER
END-XML.
DISPLAY 'PARSE DONE'.
STOP RUN.
XML-HANDLER.
EVALUATE XML-EVENT
WHEN 'START-OF-ELEMENT'
IF XML-NAME = 'item'
MOVE SPACE TO WS-ITEM
END-IF
WHEN 'CONTENT-CHARACTERS'
STRING XML-TEXT DELIMITED BY SIZE
INTO WS-ITEM
END-STRING
WHEN 'END-OF-ELEMENT'
IF XML-NAME = 'item'
DISPLAY 'GOT ITEM: ' WS-ITEM
END-IF
END-EVALUATE.
上面代码里,XML-EVENT的值是预定义的字符串,比如START-OF-ELEMENT、CONTENT-CHARACTERS、END-OF-ELEMENT等。XML-NAME保存当前元素名,XML-TEXT保存字符内容。由于是事件流,程序必须自己维护状态,例如用标志位或栈记录当前处于哪个父节点之下。
这种方式的优点是内存占用小,适合超长文档;缺点是不能随机访问,只能顺序处理。如果业务需要回看前面的节点,就必须在处理例程里把感兴趣的数据保存到工作存储区。
特殊寄存器与编码设置
COBOL为XML PARSE提供了一组特殊寄存器,最常用的是XML-EVENT、XML-NAME、XML-TEXT、XML-NNAMESPACE和XML-CODE。其中XML-CODE在解析出错时返回非零值,配合XML-DECLARE或文件状态可以判断是格式错误还是编码不匹配。
当XML文档带有声明如<?xml version="1.0" encoding="UTF-8"?>时,COBOL会按照声明自动转换,但前提是编译选项和运行的CCSID支持该编码。在EBCDIC主机上,建议显式指定XML PARSE的ENCODING选项,避免中文或特殊符号变乱码。
WORKING-STORAGE SECTION.
01 WS-XML PIC X(500).
PROCEDURE DIVISION.
XML PARSE WS-XML
WITH ENCODING 1208 * UTF-8 CCSID
PROCESSING PROCEDURE H-XML
END-XML.
IF XML-CODE NOT = 0
DISPLAY 'XML ERROR CODE: ' XML-CODE
END-IF.
上例中1208是UTF-8的CCSID编号,在z/OS上这样写可以让解析器把外部UTF-8文本转成内部格式。若文档内含namespace,可用XML-NNAMESPACE读取前缀,但多数遗留接口为了简单会去掉命名空间,直接平铺标签。
需要提醒的是,XML-TEXT在一次CONTENT-CHARACTERS事件中可能只包含部分文本,特别是长文本被分块时。所以稳妥做法是使用STRING或引用修改把XML-TEXT不断追加到目标变量,而不是只MOVE一次。
把标签映射为数据结构的实践
实际项目中,我们常把XML看成记录格式。例如一个客户报文含姓名、年龄,可以声明对应的COBOL变量,在处理例程里按元素名赋值。下面示例展示如何解析带属性的简单结构。
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-XML-DOC PIC X(300) VALUE
'<customer id="1001"><name>Zhang</name><age>30</age></customer>'.
01 CUST-ID PIC 9(6).
01 CUST-NAME PIC X(20).
01 CUST-AGE PIC 9(3).
01 CUR-ELEM PIC X(10).
PROCEDURE DIVISION.
XML PARSE WS-XML-DOC PROCESSING PROCEDURE FILL-CUST END-XML.
DISPLAY CUST-ID ' ' CUST-NAME ' ' CUST-AGE.
STOP RUN.
FILL-CUST.
EVALUATE XML-EVENT
WHEN 'START-OF-ELEMENT'
MOVE XML-NAME TO CUR-ELEM
IF XML-NAME = 'customer'
MOVE XML-ATTRIBUTES TO CUST-ID * 简化示意
END-IF
WHEN 'CONTENT-CHARACTERS'
EVALUATE CUR-ELEM
WHEN 'name' STRING XML-TEXT INTO CUST-NAME END-STRING
WHEN 'age' MOVE XML-TEXT TO CUST-AGE
END-EVALUATE
WHEN 'END-OF-ELEMENT'
MOVE SPACE TO CUR-ELEM
END-EVALUATE.
这里用CUR-ELEM记住当前元素,从而把字符内容写到正确字段。属性处理在标准里通过XML-ATTRIBUTES等寄存器获取,上面仅为示意,真实代码应使用对应API或循环取属性对。
如果XML层级深,可以用Occurs表或重定义区模拟树形。但多数COBOL程序只关心叶节点,因此扁平化映射就够用。相比生成中间文件再排序,这种内存中直接映射延迟更低。
常见错误与调试建议
新手常把XML PARSE写在循环里反复初始化,导致事件例程被多次绑定。正确做法是解析一次,在处理例程里用状态变量区分不同报文。另一个坑是忽略XML-TEXT的碎片问题,只取第一次内容造成截断。
调试时可以把XML-EVENT和XML-NAME打到日志,或者故意写错标签看XML-CODE变化。在开发环境开WTO或DISPLAY,能快速确认解析走到了哪个分支。
XML-DEBUG.
DISPLAY 'EVENT=' XML-EVENT ' NAME=' XML-NAME.
IF XML-CODE NOT = 0
DISPLAY 'ERR=' XML-CODE
END-IF.
把这段逻辑插到处理例程开头,就能看到完整的事件流。等程序稳定后删掉DISPLAY即可。对于超大文档,应测试边界长度,确保接收变量不溢出,必要时用参考修改动态截取。
总体来看,COBOL的XML PARSE虽不像DOM那样直观,但胜在原生、稳定、易嵌入现有批处理。掌握事件模型与特殊寄存器后,解析常规业务XML并不复杂。