在 2001 年,我向一位多疑的客户示范了 Web 服务可以很容易地现场部署。目的是用 Web 服务代替客户的大部分行政文书工作,从而“加强”其供给链。具有讽刺意味的是,测试中要将 Web 服务部署在客户的供应者一方,其中大多数是小型公司(15 到 50 名雇员)。
在这种情况下,使用传统的 RPC 基本上是不可能的——这就是我的客户持怀疑态度的原因。我建议,我们把 RPC 请求看作是一个 XML 文档,并使用 XML 编程而不是 RPC 编码构造调用。经过两天的测试,我们证明我的办法是可行的。
前几篇专栏文章所介绍的轻量级 XML 客户机就包含了同样的思想。在 上一篇专栏文章中,我说明了如何分析文件输出,并把它转换成 RPC 请求。这个月中我将介绍实际的编程。
回顾本文的第 1 部分,我们的最终目标是把清单 1 所示的这种遗留数据转化成 SOAP 请求。请求有一个方法,
processOrder() ,它以
Order 对象数组作为参数。
清单 1. 遗留文件示例
HDRAZ5251029200309252003 1899.00 HDRAZ5281029200310272003 149.95 LINWEBAZ525THPRE IBM ThinkPad R Series Economy 1099.00 LINDITAZ525THPRV IBM ThinkPad R Series Value 2 400.00 LINWEBAZ528BKXBE XML by Example 5 29.99 |
Order 对象在图 1 中用 UML 表示:
图 1. SOAP 数据模型
同样是在第 1 部分,您分析了这些数据并指定了两种形式之间的映射,如表 1 所示:
表 1. 分析结果
| SOAP 元素 | 导出字段 | 说明 |
| Order/Reference | HDR_ORFF | - |
| Order/Date | HDR_ODTE | 转化成 xsd:date 格式 |
| Order/Buyer | - | "Example Corp." |
| Order/Address | - | "Example Street 5, 45202 Cincinnati, OH" |
| Order/Total | HDR_OTOT | - |
| Item/Code | LIN_OCOD | - |
| Item/Description | LIN_ODES | - |
| Item/Price | LIN_OPRI | - |
| Item/Quantity | LIN_OQTY | 如果没有则为 1 |
一旦成功完成了映射分析,编写相应的 XI 规则和 XSLT 样式表就很简单了。如果您还不熟悉 XI,它使用 正则表达式(regex)预处理文本文件并转化成 XML。预处理的结果作为 XSL 处理程序的输入,后者生成最终的 XML 文档。
之所以需要进行预处理是因为 XSLT 处理程序只接受 XML 文档作为输入。
预处理程序是用一组正则表达式(称为 规则集(ruleset))。每个正则表达式都由一个匹配元素表示。预处理程序尝试对规则集中的一个正则表达式匹配源文档,如果由一个正则表达式匹配,预处理程序就生成一个 XML 元素,后面跟着更多的元素,与正则表达式中每一组对应。
在 XI 的第一个版本中,您应该通过与匹配元素相关的模式属性指定正则表达式。但是,实践证明对于很长的正则表达式这样太复杂了(很难确定 group 元素与正则表达式的关系)。为了简化编码,最新的 XI 版本允许您直接把正则表达式的一部分与 group 元素关联。一个新的 filler 元素包含不属于组的那部分正则表达式(即不生成 XML 标签)。预处理程序把所有的部分连接起来形成给定匹配元素的正则表达式。
清单 2 包含
清单 1 中文档的一个 XI 规则集和测试用的 XSLT 样式表。两个记录(
HDR 和
LIN )转换成两个匹配元素。因为字段长度是固定的,正则表达式非常简单,采用
.{5} 这样的形式,其含义是“任意字符,重复 5 次”。
清单 2. 编写 XI 规则
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xi="http://ananas.org/2002/xi/rules"
xmlns:o="http://ananas.org/2003/order"
version="1.0">
<xi:rules version="1.0"
defaultPrefix="o"
targetNamespace="http://ananas.org/2003/order"
defaultSpace="trim">
<xi:ruleset name="export">
<xi:match name="header">
<xi:filler pattern="^HDR"/>
<xi:group pattern=".{5}" name="ORFF"/>
<xi:group pattern=".{8}" name="ODTE"/>
<xi:filler pattern=".{8}"/>
<xi:group pattern=".{9}" name="OTOT"/>
<xi:filler pattern="$"/>
</xi:match>
<xi:match name="line">
<xi:filler pattern="^LIN"/>
<xi:filler pattern=".{3}"/>
<xi:group pattern=".{5}" name="ORFF"/>
<xi:group pattern=".{5}" name="OCOD"/>
<xi:filler pattern=".{5}"/>
<xi:group pattern=".{35}" name="ODES"/>
<xi:group pattern=".{2}" name="OQTY"/>
<xi:group pattern=".{9}" name="OPRI"/>
<xi:filler pattern="$"/>
</xi:match>
</xi:ruleset>
</xi:rules>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
|
Filler 适用于不作为 SOAP 请求一部分的那些字段,比如订单创建日期。在导出文件中为其他字段给出了与其名称匹配的 XML 名称(比如,
HDR_ORFF 成了
ORFF )。
注意
rules 元素上
defaultSpace 属性的使用。它自动从字段数据中去掉多余的空格。固定长度的文件总是有大量不需要的空格,要求 XI 预处理程序清楚它们可以节约时间。
为了测试正则表达式,样式表只是不加改变地复制其输入。因为样式表的输入是预处理程序的输出,样式表可以有效地输出预处理程序生成的 XML 文档。毫无疑问,这是一种保留预处理程序输出结果的复杂语法。
“全盘复制”样式表只有一个模板,是我从 XSLT 推荐标准中直接复制过来的。
使用 清单 2中的样式表验证正则表达式。除非预处理程序能够准确地从导出文件中抽取所有相关的信息,否则继续尝试建立 SOAP 请求没有什么意义。
关于 SOAP 编码,在 上一期已经讨论了很多。简而言之,过程调用、类以及它们的变量都变成了 XML 元素。编码中最复杂的部分是数组,需要两个特殊属性(在 SOAP 1.1 中):
-
xsi:type属性,它的值必须是soapenc:Array -
soapenc:arrayType属性,它的值是一个数组声明,采用了类似 Java 语言的语法——即元素类型后面跟着放在方括号内的数组大小(比如,xsi:int[5]或者po:Order[3])
SOAP 1.2 中的数组编码发生了变化。SOAP 1.2 基本上放弃了该属性,而代之以可选的
soapenc:arraySize 属性。我使用的服务器(Axis 1.1)仍然只能识别 SOAP 1.1。此外,目前部署的多数 SOAP 服务器都实现了 SOAP 1.1。
无论使用什么 SOAP 版本,过程调用都包装在 SOAP
Envelope 和
Body 元素中。
XSLT 样式表负责格式化 SOAP 请求。因为服务器需要一个
Order 对象数组,而每个
Order 可以包含一个或多个
Item 对象,在样式表中有两个循环。第一个循环是关于
HDR 记录,第二个循环则是
LIN 记录。
清单 3 给出了样式表(XI 规则集和 清单 2相同):
清单 3. 最终的样式表
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xi="http://ananas.org/2002/xi/rules"
xmlns:o="http://ananas.org/2003/order"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:op="http://ananas.org/2003/order-processor"
xmlns:order="http://ananas.org/2003/order-processor/order"
version="1.0">
<xi:rules version="1.0"
defaultPrefix="o"
targetNamespace="http://ananas.org/2003/order"
defaultSpace="trim">
<xi:ruleset name="export">
<xi:match name="header">
<xi:filler pattern="^HDR"/>
<xi:group pattern=".{5}" name="ORFF"/>
<xi:group pattern=".{8}" name="ODTE"/>
<xi:filler pattern=".{8}"/>
<xi:group pattern=".{9}" name="OTOT"/>
<xi:filler pattern="$"/>
</xi:match>
<xi:match name="line">
<xi:filler pattern="^LIN"/>
<xi:filler pattern=".{3}"/>
<xi:group pattern=".{5}" name="ORFF"/>
<xi:group pattern=".{5}" name="OCOD"/>
<xi:filler pattern=".{5}"/>
<xi:group pattern=".{35}" name="ODES"/>
<xi:group pattern=".{2}" name="OQTY"/>
<xi:group pattern=".{9}" name="OPRI"/>
<xi:filler pattern="$"/>
</xi:match>
</xi:ruleset>
</xi:rules>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="o:export">
<soapenv:Envelope>
<soapenv:Body>
<op:processOrder
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<orders xsi:type="soapenc:Array"
soapenc:arrayType="order:Order[{count(o:header)}]">
<xsl:for-each select="o:header">
<order>
<reference><xsl:apply-templates select="o:ORFF"/></reference>
<date><xsl:apply-templates select="o:ODTE"/></date>
<buyer>Example Corp.</buyer>
<address>Example Street 5, 45202 Cincinnati, OH</address>
<xsl:variable name="orff" select="o:ORFF"/>
<items xsi:type="soapenc:Array"
soapenc:arrayType="order:Item[{count(../o:line[o:ORFF = $orff])}]">
<xsl:for-each select="../o:line[o:ORFF = $orff]">
<item>
<code><xsl:apply-templates select="o:OCOD"/></code>
<description><xsl:apply-templates select="o:ODES"/></description>
<price><xsl:apply-templates select="o:OPRI"/></price>
<quantity><xsl:apply-templates select="o:OQTY"/></quantity>
</item>
</xsl:for-each>
</items>
<total><xsl:apply-templates select="o:OTOT"/></total>
</order>
</xsl:for-each>
</orders>
</op:processOrder>
</soapenv:Body>
</soapenv:Envelope>
</xsl:template>
<xsl:template match="o:ODTE">
<xsl:value-of select="substring(.,5,4)"/>
<xsl:text>-</xsl:text>
<xsl:value-of select="substring(.,1,2)"/>
<xsl:text>-</xsl:text>
<xsl:value-of select="substring(.,3,2)"/>
</xsl:template>
<xsl:template match="o:OQTY[not(text())]">
<xsl:text>1</xsl:text>
</xsl:template>
</xsl:stylesheet>
|
主模板和
o:export 匹配,这是预处理程序生成的第一个元素。它为 SOAP 信封、体和过程调用创建元素。然后是对 header 记录的循环。样式表使用
count() 函数计算数组大小。根据映射表编写字段(reference、date、buyer、address 和 total)。
第二个循环写
items 数组。为了把 line 和相应的订单联系起来,样式表使用了订单号,如条件
o:line[o:ORFF = $orff] 中所示。
注意,这里使用了一个变量(
$orff )保存 header 引用。否则因为名称相同,XPath 就会把 header 引用与 item 引用混淆了。
最后,要注意
xsl:apply-templates 指令的使用,它检索字段值。大部分字段都由默认规则处理,即复制原始数据,但是 date 和 quantity 字段有专门的模板,分别用于重新格式化日期和提供丢失的信息。
如这一系列文章所说明的那样,SOAP 和其他 RPC 标准相比有一个独有的特点:使用 XML 的文本表示。因为有更多的工具,XML 使 SOAP 比其他 RPC 标准更容易访问。
轻量级 XML 客户机,如本系列文章中所介绍的客户机,紧紧抓住这种增强的灵活性,可以为那些无力承担定制 RPC 和 Java 编程的公司部署 B2B 电子商务。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 请在
讨论论坛参与关于本文的讨论。(您也可单击本文顶部或底部的
讨论访问论坛。)
- 下载本文所用的
XI 轻量级 XML 客户机。
- 回顾 Benoit Marchal 所写的“
把文件映射成 SOAP 请求,第 1 部分”,那篇文章介绍了这个项目被后所作的分析(
developerWorks,2003 年 12 月)。
- 阅读
使用 XML 专栏早期关于轻量级客户机的文章:
- “ 一种轻量级的 XML 客户机” ( developerWorks,2003 年 9 月)启动了该项目 —— 一种用于电子商务的 XML 客户机,源于作者过去两年中从事 B2B 电子商务的经验。
- “ A first version of the lightweight client”( developerWorks,2003 年 10 月)说明如何通过 XSLT 创建 SOAP 交易。
- 看一看
XI,一个过去的
使用 XML项目,用于把文本文档导入一个 XML 发布系统(或者类似的 XML 解决方案)。
- 了解管理 header 记录和 line 记录之间关系的一种替代方案,“
递归,而非拆分,以便得胜”(
developerWorks,2001 年 7 月),Benoit Marchal。
- 尝试 IBM SDK for Web Services,它是“
IBM WebSphere SDK for Web Services (WSDK) Version 5.1”的一部分。可以使用这个 SDK 创建一个 SOAP 服务器(
developerWorks,2003 年 9 月)。
- 查看
Dave Pawson收集的关于 Muenchian 分组算法的信息,您可能会使用它管理 header 记录和 line 记录之间的关系。
- 从“
A Busy Developer's Guide To SOAP 1.1”获得关于 SOAP 编码技术方面的有益介绍。
- 如果不熟悉正则表达式,请试一试
Regex Coach工具。
- 在
developerWorks
XML 专区可以找到更多的 XML 资源。
- 了解如何才能成为一名
IBM 认证的 XML 及相关技术的开发人员。

Benoit Marchal 是一位比利时籍顾问。他是 XML by Example, Second Edition 以及其他几本 XML 书籍的作者。Benoit 能够为您的 XML 项目提供帮助。可以通过 bmarchal@pineapplesoft.com或者他的个人站点 marchal.com和他联系。