 | 级别: 初级 Kevin Williams (kevin@realworldxml.com), 首席 XML 设计师, Equient(Veridian 的一个部门)
2001 年 7 月 01 日 由开发人员兼作者 Kevin Williams 撰写的本专栏文章演示了如何使用 XML Schema 原型(和样式表)来控制用于各种表示模式的数据的样式化。用 XML、XML Schema 和 XSLT 编写的十个代码样本显示了如何运用该技术来减少代码容量和简化维护。
在前一篇专栏文章中,我描述了如何使用简单和复杂原型(请参阅侧栏
什么是原型?)来简化和使 XML 模式设计有效率。本文讨论了一个 XML Schema 原型的实际应用:在表示层用样式表使原型的表示保持一致。
表示是不是一个相当简单的过程?
表示过程有多简单取决于如何定义
简单。当使用用于数据的 XML 时,经常会需要以一致的方式来表示某种信息。例如,如果有彩色显示器时,可能想用不同颜色来表示银行帐户的余额,如果余额为正,则用黑色数字,如果余额为负,则用红色数字。这很容易,编写 XSLT 代码将特定元素值以这种方式表示。需要重申的是,到处都需要的样式代码(用于信息的所有各种视图,和用于以同样方式来样式化的每一条信息)使得剪贴错误很危险。让我们看一个简单的示例。假定您有一个清单 1 中用 XML 模式定义的文档。
清单 1. 无原型的用户声明模式
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="customer" type="customerType" />
<xs:complexType name="customerType">
<xs:sequence>
<xs:element name="customerID" type="xs:integer" />
<xs:element name="checkingBalance">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="10" />
<xs:fractionDigits value="10" />
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="savingsBalance">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="10" />
<xs:fractionDigits value="10" />
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
|
为了正确地样式化
savingsBalance 和
checkingBalance 元素,可以在 XSLT 样式表中创建一个类似于清单 2 所示的模板。
清单 2. 无原型的样本格式化模板
<xsl:template match="savingsBalance | checkingBalance">
<xsl:element name="{name()}">
<xsl:value-of select="format-number(text(), '###,###,##0.00')" />
</xsl:element>
</xsl:template>
|
 |
什么是原型?
原型是公共定义,在 XML 模式中可以跨越不同元素来共享它。在 XML Schema 规范的早期版本中,原型有它们自己的声明;然而,在发行版中,用
simpleType 和
complexType 元素实现原型。我在
上一篇专栏文章中给出了一些示例。
|
|
然而,可以看到,如果添加一个表示余额的新元素,那么就需要更改这个模板。如果有许多不同的源文档类型以及许多表示目标(譬如,无线平台、其它 XML 格式等等),这可能是个问题。您所希望做的是编写一个样式表来处理原始文档中所有余额类型的字段,而不必对创建的每个新文档重新编码。该秘密就是在 XML 模式中定义简单原型,并利用它们来节省精力。
利用原型
让我们回顾
清单 1中的 XML 结构,并添加一个表示余额信息的简单原型。该模式类似于清单 3 所示。
清单 3. 有原型的用户声明
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="customer" type="customerType" />
<xs:complexType name="customerType">
<xs:sequence>
<xs:element name="customerID" type="xs:integer" />
<xs:element name="checkingBalance" type="balance" />
<xs:element name="savingsBalance" type="balance" />
</xs:sequence>
</xs:complexType>
<xs:simpleType name="balance">
<xs:restriction base="xs:decimal">
<xs:totalDigits value="10" />
<xs:fractionDigits value="10" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
|
请注意,使用原型的 XML 模式声明更小、且更易于阅读(此点尚有争议)。您将在本专栏的后面看到,以那种方式构造模式会带来其它好处。可以利用 XML 模式就是一个 XML 文档的事实,编写样式表来将它处理成另一种形式。清单 4 展示了一个样式表,它用 XML 模式并从中创建另一个样式表。
清单 4. 样式表创建样式表
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:element name="xsl:stylesheet">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="xs:element[@type='balance']">
<xsl:element name="xsl:template">
<xsl:attribute name="match"><xsl:value-of select="@name" /></xsl:attribute>
<xsl:element name="xsl:element">
<xsl:attribute name="name"><xsl:value-of select="@name" /></xsl:attribute>
<xsl:element name="xsl:value-of">
<xsl:attribute name="select">format-number(text(),
'###,###,##0.00')</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet> |
在清单 4 的样式表中有一些要注意的事项。首先,它预先声明了 XML 名称空间,因为 XSLT 处理器需要知道您想让它与 XML 模式名称空间中声明的原始模式相匹配。其次,去掉所有元素的多余空格。由于该样式表不是用来供人阅读的,所以可以尽可能地压缩结果来节省空间。同样,请注意,由原型来驱动匹配:在原始模式文档中,没有引用特定元素。相反,它看来与类型
balance的元素匹配。除此之外,对于原型只需要包含一次实际样式代码。当用该样式表来样式化原始模式片段时,生成的表示样式表类似于清单 5 所示。
清单 5. 生成产品样式表(添加空格以增加易读性)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="checkingBalance">
<xsl:element name="checkingBalance">
<xsl:value-of select="format-number(text(), '###,###,##0.00')"/>
</xsl:element>
</xsl:template>
<xsl:template match="savingsBalance">
<xsl:element name="savingsBalance">
<xsl:value-of select="format-number(text(), '###,###,##0.00')"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
|
每当模式改变时,通过动态生成样式表,可以极大地减少代码大小和减少引入剪贴错误的几率。例如,假设您决定改变文档结构,类似于清单 6 所示。
清单 6. 使用新 moneyMarketBalance 元素的用户声明
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="customer" type="customerType" />
<xs:complexType name="customerType">
<xs:sequence>
<xs:element name="customerID" type="xs:integer" />
<xs:element name="checkingBalance" type="balance" />
<xs:element name="savingsBalance" type="balance" />
<xs:element name="moneyMarketBalance" type="balance" />
</xs:sequence>
</xs:complexType>
<xs:simpleType name="balance">
<xs:restriction base="xs:decimal">
<xs:totalDigits value="10" />
<xs:fractionDigits value="10" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
|
由于对每个模式的新版本,系统自动重新生成表示样式表,您只需通过同一个样式表(来自清单 4)运行该模式来生成以下包含
moneyMarketBalance 的样式表(再次添加了空格)。
清单 7. 生成的新模式的产品样式表(添加空格以增加易读性)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="checkingBalance">
<xsl:element name="checkingBalance">
<xsl:value-of select="format-number(text(), '###,###,##0.00')"/>
</xsl:element>
</xsl:template>
<xsl:template match="savingsBalance">
<xsl:element name="savingsBalance">
<xsl:value-of select="format-number(text(), '###,###,##0.00')"/>
</xsl:element>
</xsl:template>
<xsl:template match="moneyMarketBalance">
<xsl:element name="moneyMarketBalance">
<xsl:value-of select="format-number(text(), '###,###,##0.00')"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
|
因此,不用修改样式表,就可以在源文档中正确地实施更改(只要正确地使用原型)。这就使创建新源文档类型或改变原型的表示格式变得非常容易,而无须查找并替换数百个样式表。
复杂原型
样式化模式的策略还需要扩展到处理复杂原型。通过使用样式表来定义一个与带特定原型的元素相匹配的模板,可以进一步简化代码。来看一下另一个示例:假设您有一个文档,其模式类似于清单 8。
清单 8. 带复杂原型的文档
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="customer" type="customerType" />
<xs:complexType name="customerType">
<xs:sequence>
<xs:element name="customerID" type="xs:integer" />
<xs:element name="mailingAddress" type="address" />
<xs:element name="shippingAddress" type="address" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="address">
<xs:sequence>
<xs:element name="address1" type="xs:string" />
<xs:element name="address2" type="xs:string" minOccurs="0" />
<xs:element name="city" type="xs:string" />
<xs:element name="state" type="xs:string" />
<xs:element name="zip" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>
|
可以利用在样式表生成的样式表中定义的地址原型来确保邮件和送货地址的表示保持一致,如清单 9 所示。
清单 9. 地址原型的样式表生成器
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<xsl:element name="xsl:stylesheet">
<xsl:element name="xsl:output">
<xsl:attribute name="method">html</xsl:attribute>
</xsl:element>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="xs:element[@type='address']">
<xsl:element name="xsl:template">
<xsl:attribute name="match"><xsl:value-of select="@name" /></xsl:attribute>
<xsl:element name="p">
<xsl:element name="xsl:value-of">
<xsl:attribute name="select">address1/text()</xsl:attribute>
</xsl:element>
<xsl:element name="br" />
<xsl:element name="xsl:value-of">
<xsl:attribute name="select">address2/text()</xsl:attribute>
</xsl:element>
<xsl:element name="br" />
<xsl:element name="xsl:value-of">
<xsl:attribute name="select">concat(city/text(), ', ',
state/text(), ' ',
zip/text())</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet> |
当清单 9 中的样式表生成器按照原始模式运行时,它生成以下清单 10 中的样式表。
清单 10. 生成的地址原型的产品样式表
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="mailingAddress">
<p>
<xsl:value-of select="address1/text()"/>
<br/>
<xsl:value-of select="address2/text()"/>
<br/>
<xsl:value-of select="concat(city/text(), ', ',
state/text(), ' ', zip/text())"/>
</p>
</xsl:template>
<xsl:template match="shippingAddress">
<p>
<xsl:value-of select="address1/text()"/>
<br/>
<xsl:value-of select="address2/text()"/>
<br/>
<xsl:value-of select="concat(city/text(), ', ',
state/text(), ' ', zip/text())"/>
</p>
</xsl:template>
</xsl:stylesheet>
|
当对于不同目标媒体用相同的源 XML 文档类型时,将该技术应用于样式复杂的原型会特别有用。例如,如果正在将地址信息发送到手机来显示,那可能只包含城市和州。
结束语
本文概述了可以用原型来提高您的编码效率和正确性的方法。本篇讨论实际只涉及了该技术的表面。在大的系统中,必须支持许多表示目标(HTML、无线、其它机器用户)和许多不同的源文档类型(由于带宽限制或安全原因),正确的使用原型能够非常容易地保持样式表输出的一致性和正确性。
参考资料
关于作者  | |  | Kevin Williams 是 Equient 的首席 XML 设计师,Equient 是 Veridian 专门从事信息管理系统的 XML 设计的一个部门。他还与人合著了几本关于 XML 的书籍,由 Wrox Press 出版。可以通过
kevin@realworldxml.com与他沟通。
|
对本文的评价
|  |