tds、trt、tt、tptz、tev、timg——这些前缀全是缩写,而且都以 T 开头。
那个 T 是 Type:ONVIF 的规范文档就叫 types,前缀是同一套命名习惯。
上一节讲了概念之间的引用链, 这一节讲「这些概念在 XML 里长什么样」——也就是那些看着像乱码的前缀。 这一节的内容在第六章 XML 那一小节有底子,这里是把 ONVIF 约定的那几张表补上。
一、前缀是缩写,不是乱码
| 前缀 | 全称 | 命名空间尾段 |
|---|---|---|
tds |
Type Device Service | ver10/device/wsdl |
trt |
Type Real Time | ver10/media/wsdl |
tt |
Type Toolkit(公共类型) | ver10/schema |
tptz |
Type PTZ | ver20/ptz/wsdl |
tev |
Type Events | ver10/events/wsdl |
timg |
Type Imaging | ver20/imaging/wsdl |
第一列那个 T 是全表的共同点:
ONVIF 的规范包里有一个 types.xsd,所有 service 都从这里引类型,所以所有前缀都带 T。
tt 是唯一一个只有两个字母的,因为它不是某个 service 的前缀,而是那份公共类型本体的前缀。
所以看到 tt: 就该反应过来「这是公共类型」,看到 trt: 才知道「这是媒体服务的」。
二、一份响应里的前缀分布
把 GetProfiles 的响应里所有带前缀的标签统计一遍,只有三种前缀:
| 前缀 | 出现次数 | 在重复什么 |
|---|---|---|
tt |
21 | 参数:名字、token、分辨率、码率 |
s |
2 | SOAP 信封本身 |
trt |
2 | 方法调用和返回的外壳 |
一共 25 个带前缀的标签,分布在 3 个前缀下,响应总长 1174 字节。
平均每个标签 47.0 字节——这个比例说明 XML 的开销主要在标签本身,不在值。
第一行占了 21 个,八成以上:这就是「一个 Profile 里套了一堆参数」的直接体现,
参数全是公共类型,所以全是 tt:。
而 trt: 只出现在最外层的 Profiles 上,因为只有「这个响应属于媒体服务」这一件事需要它。
三、外壳用服务的,内容用公共的
这是 ONVIF XML 的一条硬规律,看具体报文最清楚:
<trt:Profiles token="Profile_1">
<tt:Name>main</tt:Name>
<tt:VideoEncoderConfiguration token="VideoEncoder_1">
<tt:Encoding>H264</tt:Encoding>
<tt:Resolution><tt:Width>1920</tt:Width></tt:Resolution>
</tt:VideoEncoderConfiguration>
</trt:Profiles>
trt: 只出现在第一行,往下一层全是 tt:。
规则是「外壳用服务自己的命名空间,内容用公共类型」——
因为 Profiles 这个元素是媒体服务定义的,而 VideoEncoderConfiguration 是公共类型定义的。
这条规律在写解析代码时省很多事:
你不需要逐个元素查命名空间,只要记住「外面一层是服务的、里面都是 tt」。
但 find 的路径仍然要一段段换命名空间,用通配写法容易匹配到别层去。
四、前缀只是别名,不是身份
这是本节最该记住的一句:tds: 和 foo: 若能指向同一个 URI,它们在语义上完全等价。
XML 的真实身份是「命名空间 URI + 本地名」,也就是 Clark 记法写的那个东西:
| 写法 | 在代码里 | 说明 |
|---|---|---|
<tds:GetProfiles/> |
{URI}GetProfiles |
给人看的 |
{URI}GetProfiles |
同上 | 给代码用的真身份 |
所以换前缀不改语义——只要 xmlns: 声明跟着换。
反过来说,前缀换了但声明没换,就立刻报 unbound prefix。
我试过一个更笨的错法:用字符串替换把 s: 换成 soap:,
结果连 xmlns:s= 也被改成了 xmlnsoap:s=,tds: 也被改成了 tdsoap:——
三处全坏,解析器一句话就说 unbound prefix。
这个教训的普适版是:改 XML 前缀不能用字符串替换,要按元素属性改。
五、动手 Demo:那几个前缀,其实是缩写表
Demo 把六组前缀和它们的全称、命名空间尾段对一遍,再把一份真实响应的前缀统计出来, 看哪个前缀占大头。 最后验一件事:换前缀不改语义——同一条请求换个前缀重发,设备照样认。
PFX = [("tds", "ver10/device/wsdl"), ("trt", "ver10/media/wsdl"),
("tt", "ver10/schema"), ("tptz", "ver20/ptz/wsdl"),
("tev", "ver10/events/wsdl"), ("timg", "ver20/imaging/wsdl")]
for p, tail in PFX:
print(p, tail)
# 统计一份真响应里的前缀分布
_a, b, _n = S.call(port, "media", "GetProfiles")
cnt = {}
for m in re.finditer(r"<([a-z0-9]+):", b):
cnt[m.group(1)] = cnt.get(m.group(1), 0) + 1
print(cnt, len(b.encode())) # {tt:21, trt:2, s:2} 1174
# 换前缀不改语义:把 tds 改成 foo,声明一起换
xml = S.body_of("foo", "<foo:GetDeviceInformation/>")
print(xml.count("foo:"), "unbound" not in xml)
实跑输出:
--------------------------------------------------------------------
那几个前缀,其实是缩写表
--------------------------------------------------------------------
**`tds`、`trt`、`tt`、`tptz`…… 看着像乱码,其实每一个都有全称。**
这一节把一份真实响应里出现过的前缀统计一遍,再看它们指向哪。
**ONVIF 规范里约定的前缀(按 service 分):**
前缀 全称 命名空间尾段
------------------------------------------------------------------------------------
`tds` **T**ype **D**evice **S**ervice `ver10/device/wsdl`
`trt` **T**ype **R**eal **T**ime `ver10/media/wsdl`
`tt` **T**ype **T**oolkit(公共类型) `ver10/schema`
`tptz` **T**ype **PTZ** `ver20/ptz/wsdl`
`tev` **T**ype **Ev**ents `ver10/events/wsdl`
`timg` **T**ype **Im**a**g**ing `ver20/imaging/wsdl`
**那个 `T` 是「Type」**——
**ONVIF 的规范文档就叫 `types`,所有前缀都以 T 开头,是同一套命名习惯。**
**`tt` 是唯一一个只有两个字母的**,因为它不是某个 service,
**是所有 service 共用的类型定义**(分辨率、时间、编码参数都在这里)。
**所以看到 `tt:` 就该知道「这是公共类型」, 看到 `trt:` 才知道「这是媒体服务的东西」。**
**把 `GetProfiles` 的响应里所有前缀统计一遍:**
前缀 出现次数 大体上在重复什么
------------------------------------------------------------------
`tt` 21 **参数:名字、Token、分辨率、码率**
`s` 2 **SOAP 信封本身**
`trt` 2 **方法调用和返回的外壳**
**一共 25 个带前缀的标签**,分布在 3 个前缀下。
**响应总长 1174 字节,平均每个标签 47.0 字节**——
**这个比例说明 XML 的开销主要在标签本身,不在值。**
**一个具体例子,看前缀怎么嵌:**
`<tt:VideoEncoderConfiguration token="VideoEncoder_1">`
` <tt:Encoding>H264</tt:Encoding>`
` <tt:Resolution><tt:Width>1920</tt:Width></tt:Resolution>`
`</tt:VideoEncoderConfiguration>`
**同一层里没有 `trt:`**——`trt` 只出现在最外层的 `Profiles` 上。
**这就是「外壳用服务的命名空间,内容用公共类型的命名空间」**,
**也是为什么解析时 `find` 的路径要一段段换命名空间**(06.01.04 踩过这个坑)。
统计用的是正则 <([a-z0-9]+):,
只数开标签,不数闭标签——所以 25 这个数是「标签对数」,不是「标签数」。
口径要说清,否则会和「响应里有多少个尖括号」对不上。
最后那段「换前缀」是本节的重点验证:
把 tds 改成 foo,声明也改成 foo,设备照样返回正确的响应。
这直接证明「前缀只是别名」,也解释了为什么解析代码里应该用 Clark 记法而不是前缀。
反例就在正文里:用字符串替换换前缀会同时改坏 xmlns: 声明和其他含相同字母的前缀,
这个错我犯过一次,报的是 unbound prefix,而真正的原因在替换脚本里,不在 XML 里。
30 秒实验
- 把
re.finditer的正则改成也匹配闭标签,看计数变成多少。 - 把前缀换成
soap,同时把s:也换掉,看还能不能解析。 - 只换前缀不换
xmlns:声明,看报的是什么错(应该是unbound prefix)。
常见误区
- 以为前缀有语义。 前缀只是别名,真身份是命名空间 URI + 本地名,换前缀不改语义。
- 以为
tt也是某个 service 的前缀。 它是公共类型的前缀,所有 service 共用, 所以出现次数最多。 - 用字符串替换改前缀。 会连
xmlns:声明一起改坏,必须按元素和属性改。
自测三问(点开对答案)
1. `tds`、`trt`、`tt` 分别是什么的缩写? —— **Type Device Service、Type Real Time、Type Toolkit。** **`tt` 是公共类型**,不是某个 service。 2. 为什么所有前缀都以 T 开头? —— 因为 ONVIF 的类型定义集中在一个 `types.xsd` 里,**所有 service 都从它引类型**, 前缀沿用了同一套命名习惯。 3. 换前缀会不会改变报文语义? —— **不会,只要 `xmlns:` 声明一起换。** **语义由命名空间 URI 决定,不由前缀决定。**下一节讲 Web 服务:ONVIF 里「一组地址 + 一张能力表」是怎么落成两样东西的。
- ☐ (本节完,下一节 07.02.03 Web服务)