XML/多对多关系
学习目标
编辑完成本章内容的学习,您将能够:
- 为包含有多对多关系的数据模型创建schema
- 在XML schema中使用XML包含(Include),并描述其作用
- 使用XML样式表定义数值的显示方式,同时我们也对样式表中的其他功能进行了描述
- 使用更多特定的数据类型定义数值型元素.
- 理解在解析一个包含多对多关系的数据模型时应具备何种条件
概述
编辑- 在前面的章节中,我们介绍了XML schema和XML样式表的特征,以及如何为一个一对一关系建模。在本章中,我们将会进一
步介绍能够用于描述元素标签中数据的附加数据类型。使用XML样式表,我们将会学会如何定义数据的显示方式。另外,我们 将会介绍如何处理一个多对多关系,以及如何克服在XML应用这种关系时所遇到的问题。
多对多关系
编辑图 5-1 m:m关系的数据模型
- 正如您所看到的,为了表示一个多对多关系,我们增添了两个实体。该中间实体对于关联实体数据模型的表示来说是必不可
少的,该关联实体存储了有关hotel和amenity关系的相关数据。在我们的Tour Guide例子中,我们添加了Amenity来表示 一个宾馆所拥有的可以享用的便利设施列表。这个关系即为:一个宾馆可以拥有多个便利设施、同一便利设施也可以被多家宾 馆所拥有。在XML中表示多对多关系将会遇到很多问题。我们将在本章其后的内容中讨论这些问题及相应的解决方案。
XML 模式
编辑- 随着XML模式文件尺寸的逐步扩大;为了便于使用,我们很有必要将其分割成为若干独立文档。将模式分割成为多个文档将
会使得对它的维护更加简单,并能增强其的可读性。这同时也意味着那些经常出现的元素可以在其他的模式中得到重用。这便是引入<include>元素的初衷。
- 为了校验包含其它独立模式的模式的有效性,include元素必须在其他任何元素出现之前进行声明。
1 |
<?xml version="1.0" encoding="UTF-8" ?> |
图 5-2 主XML模式– amenity.xsd
1 2 |
<?xml version="1.0" encoding="UTF-8" ?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema%22 elementFormDefault="unqualified"> |
图 5-3 包含在主模式中的其它独立XML模式– amenityList.xsd
1 2 |
<?xml version="1.0" encoding="UTF-8" ?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema%22 elementFormDefault="unqualified"> |
图 5-4 定义continent的,包含在主模式中的独立XML模式 – continents.xsd
- 注意元素continent现在是在一个独立的模式(continents.xsd)中被创建的。使用这种方式,我们才可以在其他与本例
无关的XML模式中使用continent元素。AmenityList.xsd定义出了哪些字符串是便利设施。除了这些被包含的新的模式 外,还有一些值得我们注意的地方。我们添加了附加元素<lowerPrice> 和<upperPrice>,并把它们作为元素<hotel>的 属性。这样我们便可以跟踪每一个<hotelDetails>的价格范围。这两个属性都被定义为整型数值,同时<lowerPrice>的最 小值默认为1。新元素类型“positiveInteger”用来进一步规定能够用于<population>, <lowerPrice>,和 <upperPrice>的整数的种类。
- 同时我们为<hotelDetails>添加了另外一个属性。新的<amenity>元素使得我们能够为每一家宾馆记录其所必须提供的便
利设施。一家宾馆可以拥有多个便利设施,我们添加了maxOccurs=“unbounded”作为<sequence>的一个属性。由于某些 宾馆可能在<amenityValue>处并不提供任何数值信息,因此<amenityDetails>的minOccurs限制被设定为0。
多对多关系的表示中可能存在的问题
编辑- 在XML中,表示关系的最常用的方式是父子关系。父子关系可以直接用于表示一对一和一对多关系。但是XML并不直接支持多
对多关系。由于每一个元素可能拥有多于一个父元素,因此父子关系并不适用。下面是一些可能的解决方案:
建立那些不需使用多对多关系的XML文档
- 通过限制XML文档需要传达的信息的范围,我们可以避免使用多对多关系。不同于使用单一的一个XML文档表示数据库中的所
有信息,更好的方式是将信息进行分解,以使一个文档仅仅描述多对多关系中的一个实体。在我们的tourGuide例子中,实 现上述设想的一种方式便是为每一家宾馆创建一个独立的XML文档。这样宾馆与便利设施的关系最终可以转变为一对多类型。 另外一种避免使用多对多关系的方式便是创建两个模式。第一个模式为关系建立映射,而另外一个模式则用于检验关键数据的 取值进行有效性。
采用唯一的标识符来表示多对多关系
- 为每个实体创建唯一标识它的关键字,也是一种处理多对多关系的方法,尽管它不是最方便的方法。为了采用这种方法,在
XML schema中必须定义拥有ID或IDREF类型属性的元素。ID表示父对象而IDREF则是子实体的主关键字。在tourGuide例子 中,拥有ID类型属性的元素将成为宾馆的独有标识,而IDREF元素将用来规定每一个便利设施。但是,使用这种方式会在解析 多对多关系时产生其他的问题。
去除需要多对多关系的信息
- 解决这个问题的最简单的方法就是去除需要多对多关系的信息。何时才用这种方法将主要取决于需要创建的文档以及该文档的目标读者。
- 文档设计者选择多对多关系的表示方法时,并不是只选择最有效率的信息表示方法。文档的目标读者以及该文档的使用方式等因素都必须加以考虑。
XML Schema的附加数据类型
编辑- 在前面的章节中我们介绍了XML模式的预定义数据类型(例如年、月、时间、anyURI和日期)和自定义的数据类型定义方
法。我们已经介绍了一些基础的数据类型的实例,例如整型和十进制小数类型。就像模式中的“positiveInteger”数据类 型,XML提供了许多数值型的数据类型,使用户可以选择使用它们来定义一个元素所允许取值的数字种类。下面的表格中列出 了可能的数值型数据类型:
名称 | 描述 |
byte | 带有正负号的8位整型 |
int | 带有正负号的32位整型 |
long | 带有正负号的64位整型 |
negativeInteger | 只有负值(..,-2,-1)的整型 |
nonNegativeInteger | 只有非负值(0,1,2,..)的整型 |
nonPositiveInteger | 只有非正值(.., -2, -1, 0)的整型 |
positiveInteger | 只有正值 (1, 2, ..)的整型 |
short | 带有正负号的16位整型 |
unsignedLong | 不带符号的64位整型 |
unsignedInt | 不带符号的32位整型 |
unsignedShort | 不带符号的16位整型 |
unsignedByte | 不带符号的8位整型 |
有关数值类型数据的全部正式数据类型以及其他所有的数据类型,请参阅
XML文档
编辑<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<HTML>
<HEAD>
<TITLE>World Reknowned Hotels</TITLE>
<STYLE TYPE="text/css">
H2 {TEXT-ALIGN:CENTER;}
.blueBackground {BACKGROUND-COLOR:LIGHTBLUE; TEXT-ALIGN:LEFT;
BORDER-WIDTH:0px;WIDTH:90%;}
.blackBackground {BACKGROUND-COLOR:BLACK; TEXT-ALIGN:LEFT;
COLOR:WHITE; FONT-SIZE:12pt; BORDER-WIDTH:2px;}
</STYLE>
</HEAD>
<BODY>
<H2>* * * HotelsListings by Country * * *</H2>
<xsl:apply-templates select="tourGuide"/>
</BODY>
</HTML>
</xsl:template>
<xsl:template match="tourGuide">
<xsl:for-each select="city">
<table CLASS="blackBackground">
<tr><td>
<table style="width:90%; color:white;" align="center"><tr><td>
<b><xsl:text>City: </xsl:text></b>
</td>
<td>
<xsl:value-of select="cityName"/>
</td></tr>
<tr><td><b><xsl:text>History: </xsl:text></b>
</td>
<td>
<xsl:value-of select="history"/>
</td></tr>
<xsl:for-each select="country">
<tr><td>
<b><xsl:text>Country: </xsl:text></b>
</td>
<td>
<xsl:value-of select="countryName"/>
</td></tr>
<tr><td>
<b><xsl:text>Population: </xsl:text></b>
</td>
<td>
<xsl:value-of select="format-number (population, '#,##0')"/>
</td></tr>
</xsl:for-each>
</TABLE> </td></tr>
<tr><TD colspan="2"><center>
<font style="font-size:medium">Hotels:</font>
<xsl:for-each select="hotel">
<table CLASS="blueBackground">
<tr>
<TD STYLE="FONT-SIZE:8pt;width:20%;">
<IMG>
<xsl:attribute name="SRC">
<xsl:value-of select="hotelPicture/@filename"/>
</xsl:attribute>
<xsl:attribute name="WIDTH">
<xsl:value-of select="hotelPicture/@size"/>
</xsl:attribute>
<xsl:attribute name="HEIGHT">
<xsl:value-of select="hotelPicture/@size"/>
</xsl:attribute>
<xsl:attribute name="ALT">
<xsl:value-of select="hotelPicture/@value"/>
</xsl:attribute>
</IMG>
</td>
<TD align="left" style="FONT-SIZE:10pt;">
<HR width="90%"/>
<b>
<xsl:text>Hotel Name: </xsl:text>
</b>
<xsl:value-of select="hotelName"/>
<b>
<xsl:text>Street Address: </xsl:text>
</b>
<xsl:value-of select="streetAddress"/>
<b>
<xsl:text>Telephone Number: </xsl:text>
</b>
<xsl:value-of select="telephoneNumber"/>
<b>
<xsl:text>Email Address: </xsl:text>
</b>
<xsl:value-of select="emailAddress"/>
<b>
<xsl:text>Price Range: $</xsl:text>
</b>
<xsl:value-of select="lowerPrice"/> - <xsl:value-of select="upperPrice"/>
<b>
<xsl:text>Hotel Rating: </xsl:text>
</b>
<xsl:value-of select="hotelRating"/>
<b>
<xsl:text>Amenities: </xsl:text>
</b>
<xsl:for-each select="amenities">
<xsl:value-of select="amenity"/>
<xsl:if test="position() &lt; last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
<HR width="90%"/>
</td>
</tr>
</TABLE>
</xsl:for-each>
</center>
</td></tr>
</TABLE>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
图 5-4 包含新便利设施的XML文档 – amenity.xml
XML样式表
编辑- 与第三章的输出格式类似,样式表将XML文档中的每家宾馆按照所在国家进行排列。但是在本章中我们添加了一些新的属
性。我们定义了价格范围元素、列出了每家宾馆的便利设施、而且对于每个国家的人口的显示方式也进行了重新定义以提高数 据的可读性。
XSLT函数
编辑- 在定义XML输出格式时,有可能需要对于数据进行一些简单的处理。在XML样式表中,集成的函数将会在进行数据转换时对于
数据进行处理。第三章中有一个显示一个城市中宾馆数量的例子:
- 为了增强人口信息的可读性,我们需要对于人口数据按照通常的数字书写格式为该数字分段(即3个数字一组,中间用逗号
隔开)。这样我们就需要使用format-number( ) 函数。该函数的作用就是依照特定的模式将一个数值转化成为字符串,该 特定的模式用来控制起始0的数目,以及千位之间的分割符。该函数的基本句法如下:
- number是待处理的数值
- pattern是定义了数值显示格式的字符串。字符串中的每一个字符表示一位数字,或者其他的特殊的标点符号例如逗号或减号
- 为了将<population>按照三位一组、逗号分割的格式进行显示,我们在XML样式表中的第{51}行中使用了format-number函数。
- 下面列出了当在XML样式表中使用format-number函数进行数据格式定义时,pattern中字符及其含义:
符号 |
含义 |
0 |
一位阿拉伯数字. |
- 另外在样式表中还有大量其它的函数。下面列出了一些基于数值的函数,他们在进行数值处理时非常有用:
名称 | 描述 |
ceiling() | 返回不小于指定数值的最小整数 |
floor() | 返回不大于指定数值的最大整数 |
number() | 将参数值转化为数字 |
round() | 将参数数值四舍五入 |
sum() | 返回在一个节点集中所有数值数据的数值总和 |
1 2 |
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform%22> |
图 5-5 XML样式表– amenity.xsl
- 在第125行,您将看到两个函数position()和last()。position函数返回当前内容节点在当前上下文节点集中的位置。
last函数返回在当前上下文中最后一个节点的索引值。为了列出每个宾馆的每一项便利设施,转换程序将检查节点的当前位 置是否大于上下文节点集合中最后一个节点的位置。如果返回结果为真,便在该项便利设施后添加一个逗号。
- 尽管这里的是依照第三章中的样式表构建的,但是应该注意的是嵌入式表格的使用,它进一步规定了被传送数据的格式。取
值及其标识被分别列在不同的单元格中,以便使得文档具有更好的外观和感觉。
解析过程中对于多对多关系的处理
编辑- 与您所选择的多对多关系的不同表示方法相对应,在解析过程中会出现不同的问题。当解析一个多对多关系时,您首先需要
将其分解为两个一对多关系以保证解析器可以正确处理。出于这个目的,我们必须对每个文档分别进行解析。如果您要以独立 的方式解析amenity.xml,您将发现每一个宾馆的便利设施并没有包含在已生成的插入语句中。因为我们必须首先解析定义 了每个宾馆所有可能便利设施的模式(amenityList.xsd)。然后再解析tourGuide的主模式(amenity.xsd)
- 为了避免上述情况的发生,我们需要创建一个独立的XML文档来存储每家宾馆的便利设施列表,然后首先对其进行解析,最后再解析 tourGuideXML文档。
总结
编辑- 当描述XML中的多对多关系是,我们必须首先将其分解为两个一对多类型的关系。随着XML模式文档的逐步增大,模式设计者
会发现有必要将模式分割保存在彼此独立的文档中,并且通过包含声明将其链接起来。这样做的结果是,XML 模式拥有更高 的可读性和易维护性。通过使用被定义的多种数值类型,我们可以对于数值型元素作更有针对性的描述。虽然在样式表的转换 过程中可以使用许多函数来进行数据处理,format-number()函数可以用于XML样式表来定义在处理XML时数值类型取值的显 示方式。
练习
编辑1)创建一个描述特殊类新餐馆及其所在城市的XML模式。在该模式中应该包括每家餐馆的价格范围。当描述城市时,要包含一 个country元素。使用在 http://www.opentourism.org/xml/country.xsd 中列出的国家,创建一个独立的模式,然后在您的主模式中使用<include>标签对其进行引用。并进行良构性和有效性检验。
2)使用该schema创建一个描述了至少三家餐馆而且至少有两家餐馆座落在同一城市中的XML文档。并进行良构性和有效性检验。
3)创建一个按城市排列餐馆的XML样式表。在样式表中需要记录餐馆的信息,正确定义价格范围的显示格式,并且使得表格中 的数据具有比较好的可读性。