级别: 初级 David Mertz 博士 (mertz@gnosis.cx), 演讲者, Gnosis Software, Inc.
2001 年 11 月 01 日 在本文中,developerWorks 专栏作家 David Mertz 就何时使用标记属性以及何时使用子元素内容来表示数据提出了一些建议。可以了解到设计 DTD、Schema 或(尤其是)XML 格式所要考虑的事项。您还可以了解到何时属性和内容是可互换的,何时不能。代码样本显示了这些选项。
关于 XML 的一件不同寻常的事情是,对于表示“这是数据”,它提供了几乎相同但又不完全相同的两种方式。一种表示数据值的方式是,将数据值放入子元素中;另一种方式是将数据值放入属性值中。通常,由于不能明确回答何时这两种方式要合适些,所以 XML 不是完全
正交的(计算机科学中对这个词的理解是,“每个构造做一件事情,但没有其它构造做同一件事情”)。对于何时使用子元素以及何时使用属性,本文提供了一些指导性建议。
在交给您要遵守的 XML 方言规范的时候 ― 即以 DTD 或者 W3C XML Schema,或者按非正式或按示例所描述的的方式给予您时,您就
无须确定使用哪种数据去向哪儿。如果不需要做出选择,则不要理会本文中的建议。但是,开发人员通常需要为过程
设计要使用的、确切的 XML 方言。如果您处于这种情形,则继续读下去。
要记住的一件事情是,那些仅需要良好格式的 XML 文档和那些要求对于某些 DTD/Schema 有效的文档之间的区别。有效性是非常严格的;它允许您坚持以某种方式来表示和结构化某些数据。出于非常一致的原因,确保给定文档生产过程符合有效性需求需要更多的工作。这两种方法都有一些优点;强行使用 DTD 给元素/属性问题添加了复杂性,但在这两种情况中,需要做一些权衡。下面就讨论这些权衡。
数据次序重要吗?
如果希望用 DTD,则子元素是严格有序的,而属性是无序的。在仅强调格式良好的 XML 文档中,您可以随意摆弄次序;归根到底,在这种情况下,可以将
任何标记以任何深度放入
任何其它标记中。在这两种情形中,通常属性适合于无序的数据。然而,对于带有 DTD 的 XML 文档,这种无序类型的数据几乎都需要使用属性。
例如,您可能有一个联系人的列表,其中,每个人都
必须有姓名、年龄和电话号码。但是,将年龄放在电话号码
前并没有什么逻辑含义。因此,这些属性是无序的。在这种情况中,属性更具有直观性。比较清单 1 和清单 2 中简短的 XML 文档:
清单 1. 联系人的属性数据
<?xml version="1.0" ?>
<!DOCTYPE contacts SYSTEM "attrs.dtd" >
<contacts>
<contact
name="Jane Doe"
age="74"
telephone="555-3412" />
<contact name="Chieu Win" telephone="555-8888" age="44" />
</contacts> |
清单 2. 联系人的子元素数据
<?xml version="1.0" ?>
<!DOCTYPE contacts SYSTEM "subelem.dtd" >
<contacts>
<contact>
<name>Jane Doe</name>
<age>74</age>
<telephone>555-3412</telephone>
</contact>
<contact>
<name>Chieu Win</name>
<telephone>555-8888</telephone>
<age>44</age>
</contact>
</contacts>
|
想象一下由每个 XML 格式所暗示的 DTD。对于
清单 1中面向属性的格式,DTD 可能如清单 3 所示。
清单 3. 联系人文档的属性 DTD
<!ELEMENT contacts (contact*)>
<!ELEMENT contact EMPTY>
<!ATTLIST contact name CDATA #REQUIRED
age CDATA #REQUIRED
telephone CDATA #REQUIRED >
|
做同样事情的面向子元素的 DTD 可能如清单 4 所示。
清单 4. 表示联系人文档的子元素 DTD
<!ELEMENT contacts (contact*)>
<!ELEMENT contact (name,age,telephone)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT telephone (#PCDATA)>
|
清单 4 中,DTD 的明显问题是,
清单 2中的这个简单示例在该 DTD 下是无效的(即使它有我们希望的数据)。这些子元素次序是混乱的。副栏显示了如何可以使用带 DTD 的无序子元素。但除非另有一个令人信服的原因,否则,对于无序数据,最好使用属性风格。
多个数据出现在同一层上吗?
如果在一个对象中多次出现相同类型的数据,则子元素将是更好地选择。例如,在联系人列表案例中,
contacts 对象包含许多
contact 对象。在这种情况下,很明显,应该在
contacts 元素的子元素中描述每个联系人。
然而,在实际情况中,开发人员常常会在制定修正期间逐渐偏离这一设计原则。这里,看一下它是如何发生的:首先,发现有一个 flizbam 附加在每个 Flazbar 后面(并且由数据来描述 flizbam)。好极了,这似乎是一个明显的选择,省去额外冗长的子元素,并为
Flazbar 标记创建
flizbam 属性。这样做不久 ― 在为处理 Flazbar 编写好漂亮的生产代码后 ― 发现在某些情况下 Flazbar 可以有两个 flizbam。这不是问题:对已安装的代码稍加改动,只需要更改 DTD,使其包含:
<!ATTLIST Flazbar flizbam CDATA #REQUIRED
flizbam2 CDATA #IMPLIED>
|
有了这个修改后的代码,原来的 XML 文档仍然有效,而新代码也可以起作用。过一会儿,会发现第三个 flizbam……
很难避免被引入这个设计陷阱。数据和对象会随着时间而发展,单个事物会常常成为两个或多个事物。一些 XML 程序员由于这个原因而完全避开了属性,但我认为太过分了。我的建议是,在设计阶段,就单个数据是否在以后需要多个兄弟数据,要仔细考虑。如果未来有多个兄弟数据的合理的可能性,则从一开始就使用子元素。如果有理由确信数据对象将保持唯一,则坚持使用属性。
 |
模拟无序子元素
可以创建 DTD,通过包含如这个清单中所示的定义,使清单 2 中的 XML 文档有效。
非常灵活地定义联系人列表子元素的 DTD
<!ELEMENT contact (name?,age?,telephone?)+>
|
然而,上面这个 DTD 允许太大的灵活性。造成可能有些联系人元素没有姓名,并且有些元素有几个年龄 ― 这些都不符合语义要求。要得到我们真正所希望的,将需要下面这个极其笨拙的定义。
联系人元素的笨拙但精确的 DTD
<!ELEMENT contact ((name,age,telephone)|
(name,telephone,age)|
(age,name,telephone)|
(age,telephone,name)|
(telephone,name,age)|
(telephone,age,name))>
|
这个 DTD 不好看,并且随着数据点的增多,这种难看性成阶乘增长。另外,数据生产者也并不愿看到 DTD 比语义所需要的更严格,(例如,强行使用第一个子元素 DTD)。
|
|
需要保留空白吗?
在属性规范化后,可以依靠的属性中的每个记号是通过空白来与其邻居区分开来。但是那是您可依靠的
全部。对于开发人员,为了可读性,可以向长属性值添加垂直和水平空白,这不会有任何问题(事实上,
应该这样做)。但是,一旦那些可读的属性经过 XML 解析器,属性的布局可能与源 XML 稍微有些不同。
如果空白是重要的,则子元素是较佳的选择。例如,如果表示象源代码或诗歌这样空格确实很重要的内容,则坚持使用元素内容。
可读性重要吗?
理想情况下,XML 应该是计算机阅读的格式,而不是人类阅读的格式。但是,不管幸运不幸运,程序员也属于人类;并且对于可预见的未来,我们将花大量时间阅读、编写以及调试 XML 文件。阅读格式化成仅用于机器阅读的 XML 肯定是痛苦的(无空白或无意义的空白)。
从个人角度来讲,我发现阅读和编写面向属性的 XML 格式要比面向子元素的容易得多。再看一下上面的
清单 1和
清单 2来体会我所指的含义,没有一个是难以阅读的。但清单 1 的属性版本要更容易阅读些 ― 而且也更易于编写,因为不必担心多变的子元素次序。
结束语
我已经指出了何处子元素或属性更适当的一些情况。记住了谈到的这些原则,就可以导致更清晰和更整洁的 XML 文档格式。遗憾的是,有时现实情况会涉及多种情形(指向相反的方向)。而且在许多时候,数据设计可足以导致以前动机失效。在可能的情况下,使用本文所给出的一些规则,但首先“使用(有见识的)共识”。
参考资料
关于作者
对本文的评价
|