使用开源组件smooks解析非标准EDI报文

最近项目需要使用上传EDI报文,然后解析EDI报文格式并持久化到数据库。然后在网上找了一下,有个非常强大的smooks组件可以实现这样的功能。而且不仅仅只实现从EDI -> Java的转化,还可以多种格式互相转化,唯一要做的就是配置两个非常重要的XML配置文件。

下面是关于smooks的比较详细的中文介绍:

Java的XML转换框架 Smooks


下面是smooks的下载地址,由于smooks的官网是属于河蟹的范围,然后我一直都懒得去翻墙访问了,详细的API在这里也能下载到。

smooks下载

现在的最新版本为 1.5.1


下载完成后解压,可以直接进入examples 文件夹,里面有smooks提供的所有功能的示例程序,部分示例程序使用了JUnit测试。

所以测试的时候可以直接把lib下的所有文件都导入进去。


上面就是所有的示例,可以看见smooks提供很多格式文件的转化功能,个人觉得比较常用的应该就是EDI XML CSV Java 之间的互相转换了吧。然后这次我主要关注的就是edi-to-java功能!


然后在这个示例程序里面,有标准的EDI报文格式,据带我的师傅说,这个EDI格式有很多种标准,由于这个smooks框架是国外的,所以他里面的EDI报文格式可能是国外的标准,然后我们国家有自己的EDI报文格式标准,有的公司有的行业或者又有他们自己规定的报文格式,所以这次我主要讨论比较复杂的自定义的报文格式怎么解析成JavaBean。


首先你要根据你自己的报文格式定义好各个JavaBean类。比如我的就复杂一点。我的是一个总类,总类下有公共信息,然后还有订单列表,每个订单下又有订单信息和货物信息和箱型信息列表。

文件格式如下

BillConfirm
       |- Ship
       |- List<ShipBillConfirm>
                      |- (n * Fields)
                      |- List<ShipBillConfirmCargo>
                                        |- ShipBillConfirmCargo
                                                    |- (n * Fields)
                            |- List<ShipBillConfirmCntr>
                                        |- ShipBillConfirmCntr
                                                    |- (n * Fields)

这个就比标准结构复杂一点了,其实那个BillConfirm是不用放到数据库的,关键要关心的就是多个订单ShipBillConfirm,但是由于一个EDI里面可以有多个订单信息,所以在smooks中就要使用List,所以那个BillConfirm就只是一个容器而已。Ship是所有订单的公共信息。


下面给出测试的EDI报文格式。

00:BOOKOF:BOOKING ORDER FROM:9:SWS::200912281627'
10:HENG YU:1001E::20100105:SHANGHAI'
12:SJJ:JJNH4848240ATEST1:CY/CY:P::KOBE::KOBE::P&H CO.,LTD::NOPAL INT?'L C.S.CO.,LTD TEL?:045-253-8213 FAX?:045-253-8214::SAME AS CONSIGNEE::'
41:1:O:1:1.00::1.000:N/M:SPORTWEAR'
41:2:O:2:3.00::3.000:N/M:SPORTWEAR22'
12:SJJ:JJNH4848240ATEST2:CY/CY:P::KOBE::KOBE::P&H CO.,LTD::NOPAL INT?'L C.S.CO.,LTD TEL?:045-253-8213 FAX?:045-253-8214::SAME AS CONSIGNEE::'
41:1:O:1:11.00::1.000:N/M:SPORTWEAR333'
99:9

这是一段比较简单的EDI报文格式了,其实实际中我的项目用到的比这个还要复杂一点,但是现在就只是测试和学习,我就改了一下,改得比较简单了。

下面就根据EDI报文格式和JavaBean来写配置文件。


然后smooks示例中解析的主要代码是在Main.Java中,去看代码就发现,关键的就是三个文件,一个是报文文件,这个可以我们自己修改获取的方式来解析,然后另外就是两个XML配置文件,一个是smooks-config.xml,一个是edi-to-java-order-mapping.xml。


然后就主要就是修改这两个文件。

其中smooks-config.xml是主配置文件,是用来把解析的东西来生成JavaBean的。

edi-to-java-order-mapping.xml是来匹配报文格式的配置文件。


其中smooks-config.xml的配置比较简单,和Hibernate差不多,用过的都比较容易上手。下面是我根据我的测试要求配置的:

<?xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
	xmlns:edi="http://www.milyn.org/xsd/smooks/edi-1.1.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.2.xsd">

	<!--
		Configure the EDI Reader to parse the message stream into a stream of
		SAX events.
	-->
	<edi:reader mappingModel="/shhh/edi-confirm-mapping.xml" />

	<!--
		这里就是主Bean,最后生成返回的就是这个对象;
		其中使用了<jb:wiring>的标签,这个表示的是类的引用,
		property就是类中属性的name, beanIdRef 就是对这个文件中的bean的引用。
		 然后createOnElement这个不怎么好解释,这个是和那个报文配置文件绑定的,
		 也可以理解为在哪个类中生成下面的数据。
	-->
	<jb:bean beanId="bill" class="shhh.entity.BillConfirm"
		createOnElement="BillConfirm">
		<jb:wiring property="ship" beanIdRef="ship" />
		<jb:wiring property="sbc" beanIdRef="sbcList" />
	</jb:bean>

	<!--
		这个是在BillConfirm中的Ship属性,由于是单个的,
		所以createOnElement中需要引用${bill};
		其实这个到底代表什么意思我还没有去深入了解,
		我的暂时的理解是表示这个ship是生成在bill这个beanId下的,
		但是他引用的报文配置文件中的segment的XMLtag是"Ship";
		另外的decoder就是表示这个属性用的什么数据类型;
		data 表示的是在报文配置文件中的引用,报文配置文件中对应的是XMLtag;
		<jb:decodeParam> 一看就知道了,这个是日期格式的转换配置。
	-->
	<jb:bean beanId="ship" class="shhh.entity.Ship"
		createOnElement="${bill}/Ship">
		<jb:value property="voyageNo" decoder="Integer" data="#/voyage-no" />
		<jb:value property="lineNam" decoder="String" data="#/line-nam" />
		<jb:value property="lineCod" decoder="String" data="#/line-cod" />
		<jb:value property="leavPortTim" decoder="Date" data="#/leave-date">
			<jb:decodeParam name="format">yyyyMMdd</jb:decodeParam>
		</jb:value>
		<jb:value property="EDestPortNam" decoder="String" data="#/edestportnam" />
	</jb:bean>

	<!-- 
		这个就是关键的订单列表配置了,由于是有多个的列表的形式,所以要先配置列表,
		BillComfirm中引用的也是这个列表,然后这个列表再引用ShipBillComfirm这个bean
	 -->
	<jb:bean beanId="sbcList" class="java.util.ArrayList"
		createOnElement="BillConfirm">
		<jb:wiring beanIdRef="shipbillconfirm" />
	</jb:bean>

	<!--
		这个就是关键的订单配置了,其中这个里面又配置了两个列表
	-->
	<jb:bean beanId="shipbillconfirm" class="shhh.entity.ShipBillConfirm"
		createOnElement="ShipBillConfirm">
		<jb:value property="carrierCod" decoder="String" data="#/carriercod" />
		<jb:value property="billNbr" decoder="String" data="#/billnbr" />
		<jb:value property="drTypeId" decoder="String" data="#/drtypeid" />
		<jb:value property="payWayId" decoder="String" data="#/paywayid" />
		<jb:value property="dischrgPortNam" decoder="String"
			data="#/dischrgportnam" />
		<jb:value property="destPortNam" decoder="String" data="#/destportnam" />
		<jb:value property="EShipperNam" decoder="String" data="#/eshippernam" />
		<jb:value property="consigneeNam" decoder="String" data="#/consigneenam" />
		<jb:value property="notifyNam" decoder="String" data="#/notifynam" />
		<jb:value property="notifyNam2" decoder="String" data="#/notifynam2" />
		<jb:wiring property="cargo" beanIdRef="cargoList" />
		<jb:wiring property="cntr" beanIdRef="cntrList" />
	</jb:bean>

	<!--
		货物列表信息
	-->
	<jb:bean beanId="cargoList" class="java.util.ArrayList"
		createOnElement="ShipBillConfirm">
		<jb:wiring beanIdRef="shipbillconfirmcargo" />
	</jb:bean>
	
	<!-- 
		箱型列表信息
	 -->
	<jb:bean beanId="cntrList" class="java.util.ArrayList"
		createOnElement="ShipBillConfirm">
		<jb:wiring beanIdRef="shipbillconfirmcntr" />
	</jb:bean>

	<!-- 
		单个货物信息的配置bean
	 -->
	<jb:bean beanId="shipbillconfirmcargo" class="shhh.entity.ShipBillConfirmCargo"
		createOnElement="ShipBillConfirmCargo">
		<jb:value property="seqNo" decoder="Short" data="#/seqno" />
		<jb:value property="cargoCod" decoder="String" data="#/cargocod" />
		<jb:value property="pieceNum" decoder="Long" data="#/piecenum" />
		<jb:value property="grossWeight" decoder="Double" data="#/grossweight" />
		<jb:value property="netWeight" decoder="Double" data="#/netweight" />
		<jb:value property="volNum" decoder="Double" data="#/volnum" />
		<jb:value property="marksStr" decoder="String" data="#/marksstr" />
		<jb:value property="ECargoNam" decoder="String" data="#/ecargonam" />
	</jb:bean>


	<!-- 
		单个箱型信息的配置bean
	 -->
	<jb:bean beanId="shipbillconfirmcntr" class="shhh.entity.ShipBillConfirmCntr"
		createOnElement="ShipBillConfirmCntr">
		<jb:value property="cntrNo" decoder="String" data="#/cntrno" />
		<jb:value property="cntrSizeCod" decoder="String" data="#/cntrsizecod" />
		<jb:value property="cntrTypeCod" decoder="String" data="#/cntrtypecod" />
	</jb:bean>

</smooks-resource-list>

然后下面就是关键的解析EDI报文格式的mapping文件了:

<?xml version="1.0" encoding="UTF-8"?>
<medi:edimap xmlns:medi="http://www.milyn.org/schema/edi-message-mapping-1.3.xsd">

	<medi:description name="Ship Bill Confirm" version="1.0" />

	<medi:delimiters segment="'" field=":" component="^" sub-component="~" escape="?" />

	<medi:segments xmltag="BillConfirm">
	
		<medi:segment segcode="00" xmltag="Total">
			<medi:field xmltag="edi-name" />
			<medi:field xmltag="edi-des" />
			<medi:field xmltag="edi-func" />
			<medi:field xmltag="edi-user" />
			<medi:field xmltag="edi-empty1" />
			<medi:field xmltag="edi-create" />
		</medi:segment>

		<medi:segment segcode="10" xmltag="Ship">
			<medi:field xmltag="voyage-no" />
			<medi:field xmltag="line-nam" />
			<medi:field xmltag="line-cod" />
			<medi:field xmltag="leave-date" />
			<medi:field xmltag="edestportnam" />
		</medi:segment>
		
		<medi:segmentGroup maxOccurs="-1" minOccurs="0">
		<medi:segment segcode="12" xmltag="ShipBillConfirm" maxOccurs="-1" minOccurs="1">
			<medi:field xmltag="carriercod" />
			<medi:field xmltag="billnbr" />
			<medi:field xmltag="drtypeid" />
			<medi:field xmltag="paywayid" />
			<medi:field xmltag="dischrgportcod" />
			<medi:field xmltag="dischrgportnam" />
			<medi:field xmltag="destportcod" />
			<medi:field xmltag="destportnam" />
			<medi:field xmltag="shipperid" />
			<medi:field xmltag="eshippernam" />
			<medi:field xmltag="consigneeid" />
			<medi:field xmltag="consigneenam" />
			<medi:field xmltag="notifyid" />
			<medi:field xmltag="notifynam" />
			<medi:field xmltag="notifyid2" />
			<medi:field xmltag="notifynam2" />
		</medi:segment>

		<medi:segment segcode="41" xmltag="ShipBillConfirmCargo" maxOccurs="-1" minOccurs="0">
			<medi:field xmltag="seqno" />
			<medi:field xmltag="cargocod" />
			<medi:field xmltag="piecenum" />
			<medi:field xmltag="grossweight" />
			<medi:field xmltag="netweight" />
			<medi:field xmltag="volnum" />
			<medi:field xmltag="marksstr" />
			<medi:field xmltag="ecargonam" />
		</medi:segment>

		<medi:segment segcode="51" xmltag="ShipBillConfirmCntr" maxOccurs="-1" minOccurs="0">
			<medi:field xmltag="cntrno" />
			<medi:field xmltag="cntrsizecod" />
			<medi:field xmltag="cntrtypecod" />
		</medi:segment>
		</medi:segmentGroup>
		

		<medi:segment segcode="99" xmltag="End" minOccurs="0">
			<medi:field xmltag="endNumLine" />
		</medi:segment>

	</medi:segments>
	
</medi:edimap>

其中有几个关键的键我解释一下:

<medi:description> 这个就是一个名字定义,没有太大影响;

<medi:delimiters segment="'" field=":" component="^" sub-component="~" escape="?" />

这个里面,segment 表示的是每个数据(也可以理解为每个Bean信息)的分割符,

field 表示的是每个字段的分隔符,也就是按顺序分割数据,然后保存到Bean中的属性中(按照你写配置解析EDI的顺序保存)

component 和 sub-component 暂时我还没用到,看示例里面好想是用来生成XML时的子节点。

然后还有一个关键的escape 这个是转义字符


然后我的Myeclipse不知怎么的用不了自动提示,不知道有哪些节点哪些属性可以用,害我只好把mapping的结构定义文件给下过来了 ╮(╯▽╰)╭,然后就慢慢看吧 = =。

其中<medi:segments>只能有一个,表示就是解析这个EDI文件的东西。

然后<medi:segment>就是每个解析的Bean的配置,用的就是那个每个数据的分隔符给分割的。

segcode 就是表示字段的开始字符,表示这个segmen的开始标志就是这个segcode。

xmltag 前面说过了就是和引用有关的,不能有重复的。

<medi:field xmltag="edi-name" /> 这个就是每个field字段了,根据你写这个的顺序来解析EDI并匹配到引用的xmltag中去。


然后关键的难点就是有个<medi:segmentGroup maxOccurs="-1" minOccurs="0"> 这个东西,这个在示例里面是没有的,在API里面我也没看到,愣是在结构文件里面看到的!

这个就是相当与一个循环结构,表示下面配置的segment在EDI报文可以循环匹配。其实就是我前面提到的,一个EDI可以有多个订单数据,每个订单数据下又可以有多个货物信息和多个箱型信息。

所以下面就是会循环的数据配置。

最后有两个关键的属性:

maxOccurs:这个表示的是有多少个数据匹配,-1表示可能有多个数据匹配,意思就是可以有循环的

minOccurs:这个表示的是这个segment的出现的最少次数,0表示这个segment可能是不会出现的。(如果有可能不会出现的数据,这个必须设置为0,不然在解析的时候是会报错的,因为这个好像默认是1)


最后还有一点,可能遇到EDI报文里面有一个格式字段,但是我们不需要,但是在mapping配置里面还是要配置的,你可以不引用,但是不能无视掉,所以我最后还配置了一个99 的segment,但是没有引用。

等于就是smooks执行的是严格匹配查询,不是什么你只需要哪一段数据。所以关键的就是要根据固定的EDI格式来写好配置文件。

其实写好后,就算遇到EDI格式更改,你也只需要修改一下两个配置文件就可以继续用了,扩展性还是不错的。


然后其实这个EDI解析还可以直接生成XML文件的,在示例里面有,简单的两句代码就可以了,由于我暂时还没用到这个功能所以也就没有介绍了,感兴趣的可以去下载示例源码看看。


大概就是这些,如果还有什么东西再分享吧。然后这个方面应该有相关的大神,但是不知道为什么就是没有找到相关的介绍,这个毕竟在商业交流中还是用得比较广泛的。如果有大神路过,希望不吝指教。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
EDI(Electronic Data Interchange)报文是一种基于ASCII字符集的电子数据交换标准格式。在Java中,我们可以使用EDI解析器库来处理EDI报文。常见的EDI解析器库包括Smooks和jPOS。 在解析EDI报文时,我们可以将其转换成Java对象来方便地进行后续处理。这里给出一个示例: 假设我们有以下EDI报文: ```EDI ISA*00* *00* *12*3456789012 *08*9876543210 *200101*1200*U*00401*000000001*0*T*:~ GS*PO*4405197800*9876543210*20010101*1319*1*X*004010~ ST*850*0001~ BEG*00*SA*08292233294**20010101~ N1*BY*ABC COMPANY~ N1*SE*DEF COMPANY~ PO1*1*10*EA*19.95*UK*1234567890*VP*ABC123~ CTT*1~ SE*7*0001~ GE*1*1~ IEA*1*000000001~ ``` 我们可以定义以下Java对象来表示这些数据: ``` public class EDI { private ISA isa; private GS gs; private List<ST> sts; // getters and setters } public class ISA { private String authorizationInformationQualifier; private String authorizationInformation; private String securityInformationQualifier; private String securityInformation; private String senderIDQualifier; private String senderID; private String receiverIDQualifier; private String receiverID; private String date; private String time; private String interchangeControlStandardsIdentifier; private String interchangeControlVersionNumber; private String interchangeControlNumber; private String acknowledgementRequested; private String usageIndicator; private String componentElementSeparator; // getters and setters } public class GS { private String functionalIdentifierCode; private String senderID; private String receiverID; private String date; private String time; private String groupControlNumber; private String transactionTypeCode; private String versionNumber; // getters and setters } public class ST { private String transactionSetIdentifierCode; private String transactionSetControlNumber; private String implementationConventionReference; // getters and setters } public class BEG { private String transactionSetPurposeCode; private String purchaseOrderTypeCode; private String purchaseOrderNumber; private String releaseNumber; private String date; private String contractNumber; // getters and setters } public class N1 { private String entityIdentifierCode; private String name; // getters and setters } public class PO1 { private String lineNumber; private String quantityOrdered; private String unitOfMeasure; private String unitPrice; private String basisOfUnitPrice; private String productIDQualifier; private String productID; private String vendorPartNumber; // getters and setters } public class CTT { private String numberOfLineItems; // getters and setters } ``` 然后我们可以使用Smooks或jPOS等EDI解析器库将EDI报文解析成这些Java对象。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值