级别: 中级 Benoit Marchal (bmarchal@pineapplesoft.com), 顾问, Pineapplesoft
2004 年 2 月 01 日 许多应用程序正在升级以适应电子商务交易的需要。在上一篇专栏文章中,Benoit Marchal 分析了遗留数据,并说明如何映射到目前发展水平的 SOAP 请求中。在第 2 部分中,他讨论了实现这些分析所需要的 XML 和 XSL 代码。请您在本文的 讨论论坛 上与作者和其他读者交流您的想法。
在 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 请求没有什么意义。
 |
header/line 关系的变化
header 和 line 之间的关系有两种变化很常见。第一种变化是没有 header 记录——header 信息在每一行中重复。处理这种情况的最佳方法是 Muenchian 算法(请参阅
参考资料)。
另一种变化是在 line 记录中不重复订单引用,而利用位置联系 header 和 line(新的 header 记录表示新订单的开始,在出现下一个 header 之前,所有的 line 记录都属于该订单)。要处理这些文件,您必须从侧面遍历它们,每次一个记录,如技巧文章 "Recurse, not divide to conquer" 中所述(请参阅
参考资料)。
|
|
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 编码
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 的另一个理由
如这一系列文章所说明的那样,SOAP 和其他 RPC 标准相比有一个独有的特点:使用 XML 的文本表示。因为有更多的工具,XML 使 SOAP 比其他 RPC 标准更容易访问。
轻量级 XML 客户机,如本系列文章中所介绍的客户机,紧紧抓住这种增强的灵活性,可以为那些无力承担定制 RPC 和 Java 编程的公司部署 B2B 电子商务。
参考资料
关于作者
对本文的评价
|