【51CTO独家特稿】CAM即内容装配机制(Content Assembly Mechanism),它是一个XML结构验证方法,由于它是一个新生事物,文档很少,因此本文就当扫盲了。
XML文档的验证需要确认文档是完整的,并且符合在文档类型定义(Document Type Definition 即DTD)中指定的规则,DTD是最早的规范说明方法,它提供了有用但有限的功能来验证XML文档结构,但只有一点语义;接着出现了XML Schema,它提供了更多灵活性和功能,增强了对结构的支持,并且很好地支持了语义,Schematron,RelaxNG已经尝试提升对语义的支持,但都没有取得什么进展;现在一种全新的技术在OASIS的保护下开发出来了,它就是CAM。
CAM不仅是一门schema语言,其设计目的是更好地满足业务交流和互操作性要求,它提供了强有力的机制来验证XML结构和语义,使其简洁、易于使用和易于维护;它提供了一个上下文机制 -- 一种基于XML自身其它部分或外部参数来动态调整那些应被视为有效的XML实例。
CAM是一个令人兴奋的技术,它的未来充满希望,但它是一个新技术,可能有好有坏;CAM的开发非常迅速,因此在本文中,你可能会发现很多‘在写本文的时候’的字眼。开发团队也很勤奋,你反馈的问题可能很快就会得到修复,而且有些问题可能你还没有发现就已经被修复了。
因此,在写本文的时候,CAM文档还很潦草:有一个正式的规范,一份白皮书,一份PowerPoint演示文稿和一些简要介绍了编辑器和API的网页。还没有明确的指导和教程,本文的目标就是:“CAM:缺少的手册”,扩大CAM文档阵营。
你需要
◆基本上熟悉XPath,CAM大量使用了Xpath定义业务规则,请参考w3School的Xpath教程温习一下。
◆基本上熟悉XML Schema,虽然本文表面上看起来是XML Schema的继承,因为它广泛地依赖于与XML Schema的对比,作为最有效的沟通方法,请参考w3School的XML Schema教程温习一下。
规定合法XML
XML文档是元素的多层次组合,它是一个用于存储任何数量文字或数据结构的树状存储结构,XML文档需要很好的格式,这意味着它只有一个根,其元素和属性必须符合简单的XML语法规则,在XML没有映射到特定的问题域(如数学、书籍协作或金融交易)之前,它都没什么用处,这种映射将抽象的XML区域以一种专业XML语言与你的特定问题对应起来,任何专用语义都必须事先定义好,否则就会被认为是无效的,遭到拒绝。
思考一下下面的顾客地址:
|
为了在XML Schema中验证这个XML片段,你通常会定义一个如下的结构:
|
这些限制条件指出﹤address_street﹥元素必须存在,并且必须包含在﹤address﹥元素内,还必须包含一个字符串。对于一个地址而言,一个简单的字符串值可能是适当的,但其它字段你应该使用更具体的东西,要么是一个专门的字符串(一种衍生的,有限制的字符串)、日期、整数或其它定义类型。
XML Schema是一种基于语法的系统,在它里面你需要同时为语义和结构定义语法;另一方面,Schematron是一个基于规则的系统,你可以使用规则同时指定语义和结构,即你不仅使用规则指定address_street是一个字符串,还用规则指定﹤address_street﹥必须显示在﹤address﹥元素内,XML Schema和Schematron从根本上说语义和结构都是纠缠在一起的。在编程方面耦合度很高,这是不可取的。
相比之下,CAM是一个混合系统,它将结构从语义中独立出来(低耦合),使用规则指定语义,例如address示例看起来象:
|
CAM模板的﹤as:Structure﹥小节定义了XML文档的层次结构,实际上它是从XML文档示例复制过来的,只不过将真实数据替换成占位符(以百分号标志出来)而已,因此前面的CAM模板表示是一个使用%street number and number%占位符替换真实数据的XML实例。
﹤as:Structure﹥小节补充了部分语义,它定义了一个元素包含了哪些其他元素,以及顺序,和Schematron不一样,你不需要费力地编写规则代码定义结构,CAM以所见即所得的形式指定结构,而Schematron不得不自己动手写代码,就和使用微软Word字处理软件一样方便,所见即所得形式相对使用RTF文本生成Word文档而言,编写RTF实在是太乏味、太困难了,而且容易犯错,如图1所示。
图1 |
所见即所得示例,微软Word使用更直观的形式编辑文档(左侧),但这两种方式实际上都在完成同一件事情
即使有一些很酷的工具如XmlSpy或Liquid XML Studio可以帮助你实现所见即所得的感觉,XML Schema也不是所见即所得的,思考一下下面的示例代码,它定义了一个cost,范围在1-999,保留2位小数。
|
下面显示的两个CAM语法是等同的:
|
CAM模板的﹤as:Rules﹥小节定义了在﹤as:Structure﹥小节中明确嵌入的语义之外的所有语义,包括数据类型、约束、基数、条件等。
CAM的优势
表1总结了相对于XML Schema和DTD,CAM的关键优势,表这每行将会在本文后面介绍,或以后的文章中介绍。
表1
序号 | 项目 | DTD | XML Schema | CAM | 示例/注释 |
1 | 隔离结构和业务规则 | 无 | 无 | 有 | |
2 | 当前节点固定验证 | 无 | 有 | 有 | ﹤quantity﹥将一个整数固定在1到100之间 |
3 | 当前节点条件验证 | 无 | 受限的 | 有 | ﹤zip﹥必须是5位数或10位数 |
4 | 跨节点条件验证 | 无 | 受限的 | 有 | 如果﹤state﹥是FL,NV,SD,TX,WA,WY,NH或TN,﹤taxable﹥必须是no,否则就是yes |
5 | 上下文机制 | 无 | 有 | 有 | 依赖于条件A或B是否符合 |
6 | 结构可变性 | 无 | 无 | 有 | 订购数量超过25kg的顾客必须选择一种物流运送方式 |
7 | 参数化引用 | 无 | 无 | 有 | 从加拿大采购必须符合条件x、y和z,从新西兰订购必须符合条件a、b和c |
8 | 命名空间感知 | 无 | 有 | 有 | |
9 | 定义自己的数据类型 | 不行 | 可以 | 可以 | ﹤bookNumber﹥必须是8位字符串 |
10 | 语法和文档一样 | 不行 | 可以 | 可以 | |
11 | 代码重用 | 受限的 | 可以 | 可以 | ﹤shipTo﹥和﹤billTo﹥地址包含相同的验证规则 |
12 | 工具/编辑器 | 多 | 多 | 1 | |
13 | 图形化设计器 | 多 | 多 | 无 | 使用XML Schema设计器时可以设计出复杂的结构 |
14 | 所见即所得 | 使用扩展框架 | 使用扩展框架 | 固有的 | 业务规则语句和它们执行时几乎是一致的,真正做到了按原文所见即所得, |
15 | 采用情况 | 成熟 | 成熟 | 初生婴儿 | 成熟的稳定性更好,支持也多 |
16 | API | Java、Perl、Ruby、.Net | Java、Perl、Ruby、.Net | Java | |
17 | 开放标准 | 是 | 是 | 是 |
表 1 重要的验证特性,DTD,XML Schema和CAM对比表
#p#
CAM编辑器介绍
从http://www.jcam.org.uk/下载最新版本的CAM编辑器,你可以选择下载CAM模板编辑器或Jcam引擎,本文中大部分地方你只需要CAM模板编辑器就够了(Jcam引擎执行CAM验证)。
为了启动CAM编辑器,你可能需要从零开始或从一个现有的XML文件或XSD文件创建一个模板,你会发现实际上创建一个模板还是瞒简单的,我们还是使用W3C的Purchase Order模型开始,将这个文件存储到本地,命名为po.xsd,在编辑器中,选择‘文件’?‘从模型新建模板’,指定你刚刚存储的文件的目录和文件名(如图2所示),在处理这个文件时程序可能会停顿几秒钟,处理完毕后,它会填满根元素comment区域。
图2 |
从模型新建模板对话框,指定你的XSD文件的路径和文件名,然后从模型中选择根元素,以便CAM编辑器为你创建一个基础的CAM模板
Comment元素是po.xsd文件中所有﹤xsd:element﹥节点的第一个节点(按字母顺序)的名字,这个文件包含两个节点:comment和purchaseOrder。在下面的节选中以粗体显示。你可以在清单1中查看完整的模型。
﹤xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"﹥
﹤xsd:annotation﹥
﹤xsd:documentation xml:lang="en"﹥
Purchase order schema for Example.com.
Copyright 2000 Example.com. All rights reserved.
﹤/xsd:documentation﹥
﹤/xsd:annotation﹥
﹤xsd:element name="purchaseOrder"
type="PurchaseOrderType"/﹥
﹤xsd:element name="comment" type="xsd:string"/﹥
﹤xsd:complexType name="PurchaseOrderType"﹥
﹤xsd:sequence﹥
﹤xsd:element name="shipTo" type="USAddress"/﹥
﹤xsd:element name="billTo" type="USAddress"/﹥
﹤xsd:element ref="comment" minOccurs="0"/﹥
﹤xsd:element name="items" type="Items"/﹥
﹤/xsd:sequence﹥
﹤xsd:attribute name="orderDate" type="xsd:date"/﹥
﹤/xsd:complexType﹥
...
﹤/xsd:schema﹥
清单1 Po.xsd模型
下面是从w3c获得了原始po.xsd模型,本文将使用它构建CAM模板:
﹤xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"﹥
﹤xsd:annotation﹥
﹤xsd:documentation xml:lang="en"﹥
Purchase order schema for Example.com.
Copyright 2000 Example.com. All rights reserved.
﹤/xsd:documentation﹥
﹤/xsd:annotation﹥
﹤xsd:element name="purchaseOrder" type="PurchaseOrderType"/﹥
﹤xsd:element name="comment" type="xsd:string"/﹥
﹤xsd:complexType name="PurchaseOrderType"﹥
﹤xsd:sequence﹥
﹤xsd:element name="shipTo" type="USAddress"/﹥
﹤xsd:element name="billTo" type="USAddress"/﹥
﹤xsd:element ref="comment" minOccurs="0"/﹥
﹤xsd:element name="items" type="Items"/﹥
﹤/xsd:sequence﹥
﹤xsd:attribute name="orderDate" type="xsd:date"/﹥
﹤/xsd:complexType﹥
﹤xsd:complexType name="USAddress"﹥
﹤xsd:sequence﹥
﹤xsd:element name="name" type="xsd:string"/﹥
﹤xsd:element name="street" type="xsd:string"/﹥
﹤xsd:element name="city" type="xsd:string"/﹥
﹤xsd:element name="state" type="xsd:string"/﹥
﹤xsd:element name="zip" type="xsd:decimal"/﹥
﹤/xsd:sequence﹥
﹤xsd:attribute name="country" type="xsd:NMTOKEN"
fixed="US"/﹥
﹤/xsd:complexType﹥
﹤xsd:complexType name="Items"﹥
﹤xsd:sequence﹥
﹤xsd:element name="item" minOccurs="0" maxOccurs="unbounded"﹥
﹤xsd:complexType﹥
﹤xsd:sequence﹥
﹤xsd:element name="productName" type="xsd:string"/﹥
﹤xsd:element name="quantity"﹥
﹤xsd:simpleType﹥
﹤xsd:restriction base="xsd:positiveInteger"﹥
﹤xsd:maxExclusive value="100"/﹥
﹤/xsd:restriction﹥
﹤/xsd:simpleType﹥
﹤/xsd:element﹥
﹤xsd:element name="USPrice" type="xsd:decimal"/﹥
﹤xsd:element ref="comment" minOccurs="0"/﹥
﹤xsd:element name="shipDate" type="xsd:date" minOccurs="0"/﹥
﹤/xsd:sequence﹥
﹤xsd:attribute name="partNum" type="SKU" use="required"/﹥
﹤/xsd:complexType﹥
﹤/xsd:element﹥
﹤/xsd:sequence﹥
﹤/xsd:complexType﹥
﹤!-- Stock Keeping Unit, a code for identifying products --﹥
﹤xsd:simpleType name="SKU"﹥
﹤xsd:restriction base="xsd:string"﹥
﹤xsd:pattern value="\d{3}-[A-Z]{2}"/﹥
﹤/xsd:restriction﹥
﹤/xsd:simpleType﹥
﹤/xsd:schema﹥
你实际上想要purchaseOrder作为根,因此在对话框中将根元素切换成purchaseOrder,然后点击‘确定’生成模板,此时程序会提示你保存模板,保存后模板就在CAM模板编辑器中打开了,如图3所示:
图3 |
CAM编辑器,从po.xsd模型生成模板后,编辑器同时显示了结构和规则
编辑器中的每个标签容器都涉及到一个视图,结构视图以树形结构显示XML的层次,图3显示定单有一个orderData属性和四个子节点:shipTo,billTo,comment和items。items节点可能包括多个item子节点。CAM编辑器精确地反映了基础XML CAM模板文件(PurchaseOrder/purchaseOrder_from_schema.cam),如下所示,这个文件中的﹤as:AssemblyStructure﹥小节显示的内容实际上与图3中结构视图中的信息是一致的:
﹤as:AssemblyStructure﹥
﹤as:Structure taxonomy="XML" ID="purchaseOrder" reference=""﹥
﹤purchaseOrder orderDate="%YYYY-MM-DDZ%"﹥
﹤shipTo country="US"﹥
﹤name﹥%string%﹤/name﹥
﹤street﹥%string%﹤/street﹥
﹤city﹥%string%﹤/city﹥
﹤state﹥%string%﹤/state﹥
﹤zip﹥%54321.00%﹤/zip﹥
﹤/shipTo﹥
﹤billTo country="US"﹥
﹤name﹥%string%﹤/name﹥
﹤street﹥%string%﹤/street﹥
﹤city﹥%string%﹤/city﹥
﹤state﹥%string%﹤/state﹥
﹤zip﹥%54321.00%﹤/zip﹥
﹤/billTo﹥
﹤comment﹥%string%﹤/comment﹥
﹤items﹥
﹤item partNum="%string%"﹥
﹤productName﹥%string%﹤/productName﹥
﹤quantity﹥%1%﹤/quantity﹥
﹤USPrice﹥%54321.00%﹤/USPrice﹥
﹤comment﹥%string%﹤/comment﹥
﹤shipDate﹥%YYYY-MM-DDZ%﹤/shipDate﹥
﹤/item﹥
﹤/items﹥
﹤/purchaseOrder﹥
﹤/as:Structure﹥
﹤/as:AssemblyStructure﹥
相比之下,XSD文件混合了结构和业务规则,因此维护成本更高,下面是一个完整CAM文件的顶层框架,显示了两个主要的元素:
﹤as:CAM
xmlns:as="http://www.oasis-open.org/committees/cam"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:camed="http://jcam.org.uk/editor"
CAMlevel="1"
version="1.0"﹥
﹤as:Header /﹥
﹤as:AssemblyStructure /﹥
﹤as:BusinessUseContext /﹥
﹤/as:CAM﹥
你可以在清单2中查看完整的CAM模板文件。
清单2 生成的CAM模板
从原始XML模型文件生成的purchaseOrder_from_schema.cam模板:
﹤as:CAM xmlns:as="http://www.oasis-open.org/committees/cam"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:camed="http://jcam.org.uk/editor" CAMlevel="1" version="1.0"﹥
﹤as:Header﹥
﹤as:Description﹥Generated for : purchaseOrder﹤/as:Description﹥
﹤as:Owner﹥To be Completed﹤/as:Owner﹥
﹤as:Version﹥0.1 generator v1.18﹤/as:Version﹥
﹤as:DateTime﹥2008-12-08T12:31:34﹤/as:DateTime﹥
﹤/as:Header﹥
﹤as:AssemblyStructure﹥
﹤as:Structure taxonomy="XML" ID="purchaseOrder" reference=""﹥
﹤purchaseOrder orderDate="%YYYY-MM-DDZ%"﹥
﹤shipTo country="US"﹥
﹤name﹥%string%﹤/name﹥
﹤street﹥%string%﹤/street﹥
﹤city﹥%string%﹤/city﹥
﹤state﹥%string%﹤/state﹥
﹤zip﹥%54321.00%﹤/zip﹥
﹤/shipTo﹥
﹤billTo country="US"﹥
﹤name﹥%string%﹤/name﹥
﹤street﹥%string%﹤/street﹥
﹤city﹥%string%﹤/city﹥
﹤state﹥%string%﹤/state﹥
﹤zip﹥%54321.00%﹤/zip﹥
﹤/billTo﹥
﹤comment﹥%string%﹤/comment﹥
﹤items﹥
﹤item partNum="%string%"﹥
﹤productName﹥%string%﹤/productName﹥
﹤quantity﹥%1%﹤/quantity﹥
﹤USPrice﹥%54321.00%﹤/USPrice﹥
﹤comment﹥%string%﹤/comment﹥
﹤shipDate﹥%YYYY-MM-DDZ%﹤/shipDate﹥
﹤/item﹥
﹤/items﹥
﹤/purchaseOrder﹥
﹤/as:Structure﹥
﹤/as:AssemblyStructure﹥
﹤as:BusinessUseContext﹥
﹤as:Rules﹥
﹤as:default﹥
﹤as:context﹥
﹤as:constraint action="makeOptional(//purchaseOrder/@orderDate)" /﹥
﹤as:constraint condition="string-length(.) ﹤11"
action="setDateMask(//purchaseOrder/@orderDate,YYYY-MM-DD)" /﹥
﹤as:constraint condition="string-length(.) ﹥10"
action="setDateMask(//purchaseOrder/@orderDate,YYYY-MM-DDZ)" /﹥
﹤as:constraint action="makeOptional(//shipTo/@country)" /﹥
﹤as:constraint action="datatype(//shipTo/@country,NMTOKEN)" /﹥
﹤as:constraint action="setNumberMask(//shipTo/zip,######.##)" /﹥
﹤as:constraint action="makeOptional(//billTo/@country)" /﹥
﹤as:constraint action="datatype(//billTo/@country,NMTOKEN)" /﹥
﹤as:constraint action="setNumberMask(//billTo/zip,######.##)" /﹥
﹤as:constraint action="makeOptional(//purchaseOrder/comment)" /﹥
﹤as:constraint action="makeRepeatable(//items/item)" /﹥
﹤as:constraint action="makeOptional(//items/item)" /﹥
﹤as:constraint action="setNumberMask(//item/quantity,######)" /﹥
﹤as:constraint action="setNumberRange(//item/quantity,1-999999)" /﹥
﹤as:constraint action="setNumberMask(//item/USPrice,######.##)" /﹥
﹤as:constraint action="makeOptional(//item/comment)" /﹥
﹤as:constraint action="makeOptional(//item/shipDate)" /﹥
﹤as:constraint condition="string-length(.) ﹤11"
action="setDateMask(//item/shipDate,YYYY-MM-DD)" /﹥
﹤as:constraint condition="string-length(.) ﹥10"
action="setDateMask(//item/shipDate,YYYY-MM-DDZ)" /﹥
﹤/as:context﹥
﹤/as:default﹥
﹤/as:Rules﹥
﹤/as:BusinessUseContext﹥
﹤/as:CAM﹥
规则视图(图3中高亮显示)显示了所有的业务规则,组成了模板的语义,与结构不同,规则存储在文件中时与规则视图不一样,表2将规则视图中的规则集中在一起了,在没有研究这些规则的详细情况时,你可以从它们发现:
◆规则可能是有条件的或绝对的,例如orderDate依赖于它的长度格式要求改变。
◆项目和条件是通过XPath指定的,在CAM中会广泛使用到XPath,它提供了极大的灵活性和清晰度,相比之下,XML Schema 1.0只为高级的xs:unique和xs:key concepts使用XPath。
◆规则可能适用于很广的元素范围,也可能只能适用于很少的元素,XPath支持选择文档的中任何一部分:一个元素、一个属性、所有你给定名称的元素、所有在树中确定位置的元素等。
◆规则是压缩的、简洁的、非常直观的。实际上,正如你将会看到的,编写CAM规则和编写应用程序需求是一样的。
条件 | 项目 | 行为 |
//purchaseOrder/@orderDate | makeOptional() | |
string-length(.) ﹤ 11 | //purchaseOrder/@orderDate | setDateMask(YYYY-MM-DD) |
string-length(.) ﹥ 10 | //purchaseOrder/@orderDate | setDateMask(YYYY-MM-DDZ) |
//shipTo/@country | makeOptional() | |
//shipTo/@country | datatype(NMTOKEN) | |
//shipTo/zip | setNumberMask(######.##) | |
//billTo/@country | makeOptional() | |
//billTo/@country | datatype(NMTOKEN) | |
//billTo/zip | setNumberMask(######.##) | |
//purchaseOrder/comment | makeOptional() | |
//items/item | makeRepeatable() | |
//items/item | makeOptional() | |
//item/quantity | setNumberMask(######) | |
//item/quantity | setNumberRange(1-999999) | |
//item/USPrice | setNumberMask(######.##) | |
//item/comment | makeOptional() | |
//item/shipDate | makeOptional() | |
string-length(.) ﹤ 11 | //item/shipDate | setDateMask(YYYY-MM-DD) |
string-length(.) ﹥ 10 | //item/shipDate | setDateMask(YYYY-MM-DDZ) |
表 2 编辑器中的业务规则:为定单转换XML Schema,让CAM自动生成这些规则。
#p#
CAM验证示例
现在你可以使用手中的模板验证XML文件,W3C网站上除了提供定单模型外,还提供了一个定单实例(PurchaseOrder/po.xml),但下载下来的会有一个印刷错误,图4高亮显示了错误,如果你尝试打开或验证畸形的XML文件,CAM编辑器会显示堆栈转储信息和错误消息(也看图4),并拒绝载入文件。
图4 |
畸形XML文件:这个图显示了为什么原始的po.xml文件不是合适的,将其载入CAM编辑器时显示出其错误
当你通过将感叹号和左半边尖括号对换位置修复这个错误后(正确的文件是PurchaseOrder/po_corrected.xml),你可以使用CAM编辑器载入这个XML文件,CAM编辑器以XML视图形式显示这个文件,绘制成如结构视图那样的树状结构,如图5所示,目前在模板中相同的元素显示的是真实的值而不是占位符。
图5 |
XML视图:当你打开一个XML文件时,以XML视图形式显示树形结构,可以折叠和展开
为了验证文档,选择‘运行’?‘运行JCam’,你将会看到如图6所示的Jcam运行对话框,默认情况下,Jcam选择载入的XML文件,应该可以通过它的结构ID如purchaseOrder(这个结构的根)来识别,点击‘完成’关闭这个对话框开始验证,结果显示在主窗口中下方的运行结果视图中,注意验证过程发现了两个错误,尽管在图6中只显示了一个,如果你仔细一看,你会发现有错误的节点上会有一个黄色或红色的图标,在本例中,错误发生在﹤zip﹥元素上,它的父元素﹤shipTo﹥也显示了一个错误图标,甚至根元素﹤purchaseOrder﹥也显示了一个错误图标。同样,你可以推断第二个错误是隐藏在﹤billTo﹥元素中的。
图6 |
执行验证:验证结果显示在运行结果视图中,每个验证失败的元素或属性都有一个错误标记,它的上级元素就有一个警告标记
这个XML文件在任何XML Schema编辑器中验证都没有错误,为什么在这里验证就失败了呢?运行结果视图中的错误指出zip代码根据CAM模板的定义是无效的,这个模板会检查是否是一个浮点数,因为在美国zip代码要么是5位要么是9位的整数,zip代码的CAM模板规则来自XSD规格说明XSD规格说明简单说明了zip代码是一个十进制数,这一点你可以从清单1中看到:在USAddress复杂类型中查找zip字段,CAM模板生成程序应该避免不用的输入输出。但你可能不同意,我提交的XSD规格说明太宽松了,数据类型应该是一个整数而不是一个十进制数,下面的部分将会介绍如何使用CAM编辑器来纠正这个错误。
当你按照本文的例子进行研究时,你可能会遇到模板没有象预期那样运转,在这种时候要检查两样东西:
◆点击‘工具’→‘验证CAM模板’菜单项查找所有问题。
◆如果你在运行JCam对话框中点击‘完成’按钮,似乎什么事情都不会发生,按‘取消’关闭对话框,然后查看控制台视图中的错误消息,例如,如果你忘记指定要验证的XML文件了,对话框不会禁用完成按钮,控制台视图中报告的错误是‘模板是空的’,这多少会让人有些误解。如果控制台视图什么都没有显示,那就表示一切ok。
#p#
创建业务规则
在结构视图中选择﹤shipTo﹥元素下的﹤zip﹥元素,附加到这个元素的规则显示在项目规则(ItemRules)视图中,在本例中只有一个规则,使用的是setNumberMask谓词。在类别(category)列中的规则上点击右键打开这个规则的上下文菜单,然后选择‘编辑规则’,打开编辑约束规则对话框,如图7所示。
图7 |
编辑约束规则:为了修复setNumberMask谓词附加到//shipTo/zip元素,选择结构视图中的元素,打开它的上下文菜单,选择编辑规则打开编辑约束规则对话框,点击数字特征码字段明确指定特征码
在数字特征码字段上点击,打开另一个对话框编辑特征码,现在只需要将######.##修改为#####即可,关闭这两个对话框,在主编辑器窗口中,你会看到更新后的规则,重新执行一次验证,//shipTo/zip错误应该不会再出现,只留下//billTo/zip错误,很明显这是一个相同的错误,因此你可以使用相同的手段修复它,但因为//billTo/zip和//shipTo zip的值应该一样,这样就可以使用一个通用的规则而不用每个指定一条规则了,本文的第二部分将会详细地介绍如何使用通用规则。
规则更新后你也应该更新占位符(图7中的项目1),如果你和图6比较,你会发现值从%54321.00%变成%54321%了,它更能代表zip代码,在这个特殊的例子中,元素的占位符和关联的规则的紧密相关的,假设它们自动相互跟踪是合理的,但在许多情况下,关系并不是直接的,元素和规则是多对多的关系:你可以对一个元素应用多个规则,或者一个规则应用给多个元素。
为了更新图7所示的元素占位符,在结构视图中//shipTo/zip字段上打开上下文菜单,选择‘编辑文本’,在对话框中将54321.00%修改成%54321%。
占位符为两个角色服务,CAM处理程序单独使用它确定某个元素的内容是否已被修复,这是由围绕在占位符两边的百分比符号确定的,注意在更新元素的占位符前,你要重新验证//shipTo/zip字段,确认在百分比符号中间的值被CAM处理程序忽略。
百分比符号之间的值应该是准确、简明地指出包含什么元素,通常上下文已经为你完成了大部分工作:元素的名字是‘zip’,在美国它会被立即认为是5、9或10位整数,通过设置占位符为%54321%,你告诉用户模板只接受5个字符的zip代码。
强度测试验证
现在你已经更新了占位符和规则,但只修改这两个地方就足够验证zip代码了吗?为了测试它,你需要为CAM处理程序提供不同的测试用例,最简单的方法是打开包含你要验证的数据的XML视图,修改//shipTo/zip的值,然后再重新验证,你可以在XML视图中象结构视图那样编辑节点:打开上下文菜单选择编辑文本,确定最小值以便覆盖到所有范围,让每个值都被验证一次,表3提供了这样一个列表,从这个表中可以看到有两个地方一个是pass掉,而另一个却没有过,只有这两个验证函数都过了才行。
//shipTo/zip | setNumberMask(#####) | setStringMask(00000) |
90952 | Pass | Pass |
90952.1 | Fail | Fail |
123456 | Fail | Fail |
90952-1234 | Fail | Fail |
1 | Pass | Fail |
(blank entry) | Fail | Fail |
90952a | Fail | Fail |
-12345 | Pass | Fail |
(123) | Fail | Fail |
表 3 zip代码测试用例:这个表显示了使用数字型特征码#####和使用字符串型特征码00000进行验证的结果,结果以绿色表示的是正确的,以红色表示的是错误的
这两个测试都通过具有相同的原因:特征码是数字,这两个测试都是有效的数字。虽然zip代码只包含数字,实际上它是一个字符串,从数字上来说,00001和1是相等的,在zip代码域中,00001代表一个有效的代码,而1不是。因此要使用numeric特征码代替textual特征码,为//shipTo/zip打开编辑约束对话框,将行为从setNumberMask修改成setStringMask,在String Mask字段上点击打开特征码编辑器,输入5个0,或者按数字0-9之间任何一个数字5次,然后退出这两个对话框,如果你现在重新验证表3中的每个测试用例,你会发现它们所有的测试结果都是正确的。
在正式的CAM规格说明书的3.4.3节(CAM内容特征码语法)列出了有效的特征码字符,表4就是改变自它的。
表 4 特征码字符:当规则行为需要特征码时,这些字符有特定的含义
字符 | 描述 | |
字符串特征码 | ||
X | 任何字符,强制性 | |
A | 强制性字母数字字符或空格 | |
a | 非强制性字母数字字符或空格 | |
? | 任何单一字符 | |
* | 0或更多字符0 | |
U | 一个可以被转换为大写的字符 | |
^ | 大写,非强制 | |
L | 一个可以被转换为小写的字符 | |
_ | 小写,非强制 | |
0 | 数字、后缀和前端插入的0,前端插入的正负号 | |
# | 数字、后缀和前端插入的0被取消,前端插入的正负号 | |
' ' | 转义字符 | |
数字型特征码 | ||
0 | 数字、后缀和前端插入的0,前端插入的正负号 | |
# | 数字、后缀和取消前端插入的0,前端插入的正负号 | |
. | 小数点 | |
J | 特征码的第一个字符,可能调用Java格式化方法处理特征码,当传递给Java时,文字J被忽略 | |
日期型特征码 | ||
DD | 一月中的某天 | |
DDD | 一年中的某天 | |
DDDD | 一月中的相对某天 | |
MM | 一年中的月份 | |
MMM... | 月名,如January(一月),字段会被填充或截断成3-10个数字 | |
YY | 两位数的年 | |
YYYY | 四位数的年 | |
W | 一周的某天 | |
WWW... | 期名,字段会被填充或截断成3-10个数字 | |
/ | 斜号,日期分隔符 | |
- | 连字符,另一个日期分隔符 |
如果你想找一个具有完整、清晰文档,并且所有的bug都被消除的工具,那这个可能会让你失望,但你如果不介意宝石周围那一点点瑕疵,我相信在你的兵器库中CAM会是一个伟大的工具,最后,我要告诉那些热心的开发者们,CAM编辑器和CAM引擎最近的版本(我用的是1.6.2版)中的行为完全是合理的。
这是第一部分的内容,到此就结束了,现在你至少知道其实使用CAM设计时还是很简单的,在本文的第二部分中,你将会看到CAM的强大之处,另外,你还将会看到关于开发模板和规则使用的技术更深入的讨论,包括:通用结构和通用规则,基于内部或外部因子的条件验证,详细比较XSD关于数据类型、排序和基数。最后还将介绍如何避免常犯的错误。
【编辑推荐】