2. XML语法入门:标签、属性、命名空间与解析基础

说实话,URDF 本质上就是一个 XML 文件。你想想看,机器人身上那么多连杆、关节、传感器,总得有个结构化的方式把它们描述清楚。XML 就是干这个的。我最早接触 URDF 时,以为它是什么高深莫测的机器人语言,后来才发现——说白了,就是一套写得很规矩的 XML。

这一章咱们就把 XML 的核心语法捋一遍。不用怕,内容很实在。你只要掌握标签、属性、命名空间和基本的解析方法,后面写 URDF 就跟搭积木一样顺手。

XML 语法核心 标签 & 属性 元素结构 / 属性赋值 命名空间 xmlns / 避免冲突 解析基础 DOM / SAX / 树结构 空元素 属性类型 默认空间 前缀空间 DOM 树 SAX 流 掌握这三块,URDF 语法就通了八成

2.1 XML 标签与属性 —— 骨架和血肉

XML 文档里,标签就是骨架。每个标签代表一个元素,比如 <link>、<joint>。属性则是附着在标签上的键值对,用来描述元素的细节。我个人的习惯是:先把标签搭好,再往里填属性,就像先画好零件再拧螺丝。

一个简单的例子:

<robot name="my_robot">
    <link name="base_link">
        <visual>
            <geometry>
                <box size="0.2 0.3 0.1"/>
            </geometry>
        </visual>
    </link>
</robot>

你看,<robot> 是根标签,name 是它的属性。<link> 又嵌套了 <visual>,一层层像套娃。属性值必须用引号括起来,单引双引都行,但我建议统一用双引号,避免混用。

标签的两种写法:

  • 成对标签:<link> ... </link>,中间可以嵌套其他元素或文本。
  • 空元素标签:<box size="0.2 0.3 0.1"/>,没有子元素,自闭合。

我在项目里见过有人把空元素写成 <box></box>,也能解析,但不够清爽。自闭合更符合工程习惯。

属性命名风格:URDF 里属性名通常用下划线连接(比如 visual_origin),但 XML 本身对大小写敏感。我踩过坑:name 写成 Name,解析直接报错。记住,XML 里 name 和 Name 是两个东西。

2.2 XML 命名空间 —— 避免“撞名”

命名空间(namespace)这东西,一开始我觉得挺绕。说白了,就是给标签加个“姓氏”,防止不同语言里的同名标签打架。比如 URDF 里有个 <link>,SVG 里也有 <link>,混在一起就乱了。

命名空间通过 xmlns 属性声明。看个例子:

<robot xmlns:urdf="http://www.ros.org/urdf">
    <urdf:link name="arm">
        <urdf:visual>...</urdf:visual>
    </urdf:link>
</robot>

这里 xmlns:urdf 定义了一个前缀 urdf,后面所有带 urdf: 的标签都属于这个空间。如果不加前缀,那就是默认命名空间。

命名空间形式写法典型场景
默认命名空间xmlns="http://..."URDF 根标签
带前缀命名空间xmlns:prefix="http://..."混合多种 XML 格式
我曾经在写 URDF 时给 <robot> 加了一个默认命名空间,结果 Gazebo 死活不认。后来发现 URDF 解析器对命名空间很敏感,除非你明确需要混合格式,否则别乱加。URDF 标准里一般不要求显式声明命名空间,保持简洁就好。

2.3 XML 解析基础 —— 机器怎么读你的文件

你写好的 URDF,机器人系统怎么理解?靠的就是 XML 解析器。解析器把文本变成一棵“树”,我们叫它 DOM 树。每个标签变成一个节点,属性变成节点的属性值。

两种主流解析方式:

  • DOM 解析:一次性把整个 XML 读入内存,构建成树。优点是方便随机访问,缺点是文件大了会吃内存。URDF 模型通常不大,所以 ROS 里用的就是 DOM 解析。
  • SAX 解析:流式解析,一边读一边触发事件。适合超大文件,但你不能回头找某个节点。我早期做数据采集时处理过上百 MB 的日志,用的就是 SAX。

在 Python 里,解析 URDF 通常用 xml.etree.ElementTree,它属于 DOM 风格。下面是一个极简的解析示例:

import xml.etree.ElementTree as ET

tree = ET.parse('robot.urdf')
root = tree.getroot()

print('根标签:', root.tag)
print('机器人名字:', root.attrib['name'])

for link in root.findall('link'):
    print('连杆:', link.get('name'))

你看,几行代码就能把 URDF 里的连杆名字全捞出来。我个人习惯先用 findall() 定位元素,再用 .get() 取属性,清晰又不容易出错。

避坑指南(我亲身经历):

  • 解析时如果 XML 文件有语法错误(比如少了一个闭合标签),ET.parse() 会直接抛异常。所以写 URDF 时一定要保证标签成对。
  • 属性值里不要用中文空格或特殊字符,否则解析出来的字符串可能带乱码。我遇到过因为复制粘贴了不可见字符,查了半天。
  • 注释 <!-- 注释 --> 可以写,但解析器会忽略它们。别把重要信息放在注释里。

2.4 小结 · 心里有树,写 URDF 不怵

XML 语法真的不难。标签是骨架,属性是血肉,命名空间是姓氏,解析器是翻译官。你只要记住:结构清晰、属性规范、命名空间不乱用,URDF 写起来就顺了。

下一章我们会直接进入 URDF 的核心元素,到时候你会发现——原来都是 XML 那点事儿。