属性是附着在开始标签上的名字-值对,
<book sku="A-1">里的sku就是属性。它只能装简单文本值,不能嵌套结构,同元素内不能重名。数据放属性还是放子元素,是 XML 设计里最高频的取舍题——本节给出一套可复用的决策方法,并用一次接口演进复盘说明选错的代价。
<price currency="CNY">128.00</price> <!-- 属性写在开始标签内:名字 = 引号括起的值 单引号双引号均可,但同一文档风格要统一 --> <line sku="A" qty="2"/> <!-- 空元素同样可以携带属性 -->
三条硬边界:值只能是字符串——想表达"数量是整数"这个类型信息,属性自己做不到,得靠第 3 章的 XSD 从外部约束;不能装子结构——属性值里就算写出尖括号,也只是被转义后的普通文本,不会被当成标记;同名冲突——一个开始标签里写两个同名属性直接语法报错。
另有一条软边界值得记:属性值里的空白会被解析器规范化(换行、制表符折叠成单个空格),指望在属性里存格式化文本是行不通的。
| 判断维度 | 倾向属性 | 倾向子元素 |
|---|---|---|
| 数据形态 | 简单标量:id、编号、单位 | 有结构或可能长出结构 |
| 语义角色 | 元数据:描述"这份"数据的属性 | 数据本身:业务要取用的值 |
| 可重复性 | 一条只出现一次 | 可能出现多条 |
| 可扩展性 | 几乎不可扩展 | 随时加子元素 |
一句话心法:标识用属性,内容用子元素。书的 ISBN、订单号是"铭牌";书名、金额是"货"。
背景:早期接口把金额做成属性,因为"看起来干净"。初版:
<order id="O1001"> <item sku="A" price="128.00">机械键盘</item> </order>
操作与结果:一年后业务要求金额必须携带币种与是否含税。属性装不下结构,只能加两个并列属性:
<item sku="A" price="128.00" currency="CNY" taxInclusive="true">机械键盘</item> <!-- 三条属性之间的一致性没有结构保证:currency 出现了、taxInclusive 忘了写,谁管? -->
解读:价格从"简单标量"演化成了"带结构的复合值",正撞在决策表的第四行——当初就该预判金额是业务数据而非元数据。重构版把金额升为子元素组:
<item sku="A"> <name>机械键盘</name> <price> <amount>128.00</amount> <currency>CNY</currency> <taxInclusive>true</taxInclusive> </price> </item> <!-- amount、currency、taxInclusive 捆绑在 price 下,第3章的 XSD 可整体约束 -->
变式:如果接口消费者众多、无法一次性切换,通常加版本号并行两代结构,但并行期的双份维护成本正是当初决策错误的账单。这个案例的教训不是"永远别用属性",而是问一句:这个值五年后还是标量吗。
⚠️ 反面教材是把所有数据都塞进属性——"一行一个元素"看似紧凑,实则丢失了结构、无法扩展、XPath 写起来全是
@,是典型的用 XML 之形、毁 XML 之实。
有一类属性是语言保留的:以 xmlns 开头的命名空间声明。它们不是业务数据,是给元素贴"户口"的官方铭牌,第 4 章会专门解剖。这里先记一条边界:自定义属性不要以 xmlns 开头,也不要叫 xml 打头的保留名。
决策表要用起来才长在身上。五条真实数据,先自行判断属性还是子元素,再对照分析:
isbn——标识,天然标量,属性。书名——业务内容,子元素。作者列表——可能多人,天然可重复,必须是子元素(属性装不下多条)。出版日期——业务内容,子元素;且它将来可能长出"印刷版日期、电子版日期"的分化,属性更加不合适。版本号——有趣的边界案例:若仅用于内部追踪、永远单值,属性轻便;若要参与对外语义(不同版本不同内容),子元素更稳妥。五条练完可以提炼出更细的手感:先问重复性(可重复直接子元素),再问演进性(会长结构的子元素),最后才是简洁偏好——前两问是客观判据,第三问才轮到口味。
顺带回应一个高频疑问:"属性那么多限制,为什么语言还要设计它?"因为标识与内容的分离本身携带语义:读者扫一眼开始标签就能拿到全部标识信息,不必下钻子元素;工具(校验器、编辑器联想、XPath 的 @ 写法)也都围绕这个约定提供快捷通道。属性不是残缺的元素,是另一种职责。
这个案例还有一个值得单独点出的副产品:接口演进时,属性是比子元素更脆的承诺面。属性名一旦对外发布,改名的破坏力与元素改名相同,但属性没有"加子结构"的缓释空间——元素可以往深处长,属性只能原地改。所以对外契约里属性越少、越偏纯标识,未来版本留给双方的余地越大。把这条与决策表合起来念:属性放标识与元数据,不仅因为语义合适,也因为标识和元数据天然最稳定——两个判据在同一个方向上使劲,决策就更笃定。
再补一条排错经验。属性值里的引号嵌套是高频翻车点:外层用双引号后,值内再出现双引号只能写 ",直接粘贴路径或 SQL 片段进属性几乎必炸;同理 &、< 在属性值里也必须转义,Windows 路径、URL 查询串(a=1&b=2)尤其容易中招。另一个坑是空白规范化(XML 规范规定解析器会把属性值里的换行与连续空格折叠成单个空格),把多行模板、缩进代码存进属性,读回来全成了单行——这类"数据悄悄变形"的 bug 比报错更难查,因为没有任何工具会提示你。
骨架的承重件与铭牌都已解剖完,剩下的两类旁白——写给人的注释、写给程序的处理指令——在下一节一并处理。