XML是一种树形结构的标记语言,它的设计初衷是描述层级化的数据,因此语言本身并没有像JSON那样提供原生的数组类型。但这并不妨碍我们用XML来表达列表和集合,实际上业界已经沉淀出了好几种成熟的写法。选哪种写法,往往取决于数据的复杂度、是否需要严格校验、以及解析端用什么工具。下面逐一分析这些表示法的结构和适用场景。

重复元素法:最通用的列表写法
重复元素法是XML中表示列表最常见、也最推荐的方式。它的核心思路是:定义一个父元素作为容器,然后在其中放置若干个同名的子元素,每个子元素承载列表中的一项数据。这种写法完全符合XML的树形模型,任何XML解析器都能原生支持。
一个典型的写法如下:
<users>
<user>张三</user>
<user>李四</user>
<user>王五</user>
</users>如果列表项本身是一个复杂对象,而不仅仅是简单值,可以让每个重复的子元素携带自己的属性或下级节点:
<books>
<book id="1">
<title>Java编程思想</title>
<price>89.00</price>
</book>
<book id="2">
<title>深入理解计算机系统</title>
<price>139.00</price>
</book>
</books>这种写法的优势非常明显:结构清晰、可读性强,每一项数据独立成节点,增删改查都很方便。当列表项需要携带多个字段时,重复元素法几乎是唯一合理的选择。它的缺点是标签数量多,文件体积比紧凑格式大,在海量数据传输时可能显得冗长。不过在网络带宽充裕的今天,这个缺点通常可以忽略。
分隔符法:把多个值塞进一个节点
当列表项都是简单的标量值时,可以用一个元素或属性承载多个值,值之间用约定的分隔符隔开。常见做法是用空格、逗号或分号分隔:
<config>
<!-- 用属性存多个值 -->
<server ports="8080,8443,9000"/>
<!-- 用元素文本存多个值 -->
<keywords>java,xml,list,array</keywords>
<!-- 空格分隔也是常见约定 -->
<whitelist>192.168.1.1 192.168.1.2 192.168.1.3</whitelist>
</config>这种写法的优点是紧凑、简洁,特别适合配置文件中的小规模列表。但它有一个硬伤:如果某个值本身包含分隔符,就需要引入转义机制,处理起来很麻烦。此外,解析端拿到字符串后还要自己拆分、去空格、做类型转换,增加了额外的工作量。
值得注意的一个变体是带索引的编号写法,即用item0、item1、item2这样的元素名来区分列表项。这种写法在一些老旧的Java配置文件里偶尔能见到,但它破坏了“同名元素”的规约,XPath选取和Schema校验都不方便,现代设计中不建议采用。
用Schema为列表结构加上校验
无论采用哪种表示法,如果希望列表结构得到严格约束,就需要借助DTD或XML Schema(XSD)。在XSD中,重复元素法通过maxOccurs属性来声明,设置为unbounded表示允许出现任意多次:
<xs:element name="users">
<xs:complexType>
<xs:sequence>
<xs:element name="user" type="xs:string"
minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>对于分隔符法,XSD提供了专门的xs:list简单类型,它本身就约定了多个值之间用空格分隔:
<xs:simpleType name="portList">
<xs:list itemType="xs:integer"/>
</xs:simpleType>定义了portList类型之后,属性值形如ports="8080 8443 9000"就会被校验为整数列表,任何一个不是整数的值都会导致校验失败。需要留意的是,xs:list的标准分隔符是空格,如果想用逗号分隔,就得自己定义校验规则或改用重复元素法。这也是为什么很多规范(比如SVG的points属性)统一采用空格加逗号混合的约定。
主流语言如何解析XML列表
表示法定好之后,解析端代码怎么写同样重要。以重复元素法为例,Java中使用DOM方式可以直接按标签名取出所有节点,返回的本身就是一个有序列表:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("books.xml"));
NodeList bookNodes = doc.getElementsByTagName("book");
for (int i = 0; i < bookNodes.getLength(); i++) {
Element book = (Element) bookNodes.item(i);
System.out.println(book.getAttribute("id"));
}Python这边用标准库xml.etree.ElementTree更加简洁,findall方法配合路径表达式即可拿到所有同名子元素:
import xml.etree.ElementTree as ET
tree = ET.parse("books.xml")
root = tree.getroot()
titles = [book.find("title").text for book in root.findall("book")]
print(titles)如果用的是分隔符法,解析逻辑则变成先取值再拆分,例如root.find("keywords").text.split(","),记得处理前后空格和空值。综合来看,重复元素法配合maxOccurs校验,是结构化数据交换的首选;分隔符法适合简单配置;两者混用时,在项目文档中明确约定分隔符和层级规则即可避免歧义。