命名空间速成课程

作为一种 XML 方言,SVG 有命名空间。如果您打算编写 SVG 内容,了解命名空间的概念及其用法非常重要。命名空间对于支持多种 XML 方言的用户代理至关重要;浏览器必须非常严格。现在花时间了解命名空间将使您免受以后的麻烦。

背景

各种 W3C 规范的一个长期目标是,使不同类型的基于 XML 的内容能够混合在同一个 XML 或 HTML 文件中。例如,SVG 和 MathML 可以直接合并到基于 HTML 的科学文档中。能够像这样混合内容类型有很多优点,但它也需要解决一个非常实际的问题。

当然,每个 XML 方言都定义了其规范中描述的标记元素名称的含义。在单个文档中混合来自不同 XML 方言的内容的问题是,一种方言定义的元素可能与另一种方言定义的元素具有相同的名称。例如,HTML 和 SVG 都有一个 <title> 元素。用户代理如何区分这两个元素?CSS 样式如何区分这两个元素?事实上,用户代理如何判断内容是它知道的,而不是一个无意义的未定义 HTML 自定义元素或一个包含用户代理不认识的任意元素名称的 XML 文件?

与普遍看法相反,这个问题的答案不是“它可以从 DOCTYPE 声明中判断出来”。DTD 从未为混合内容而设计,过去试图创建混合内容 DTD 的尝试现在被认为已经失败。XML 和一些 XML 方言(包括 SVG 和 HTML)不要求 DOCTYPE 声明。SVG 1.2 甚至没有 DOCTYPE 声明。事实是,DOCTYPE 声明(通常)与单一内容类型文件中内容匹配,这仅仅是巧合。DTD 仅用于验证,而不是识别内容。任何使用其 DOCTYPE 声明识别 XML 内容的用户代理都是不可靠的。

这个问题的真正答案是,XML 内容通过给出显式的“命名空间声明”来告诉用户代理元素名称属于哪个方言。

声明命名空间

那么这些命名空间声明是什么样的,它们放在哪里?下面是一个简短的示例。

svg
<svg xmlns="http://www.w3.org/2000/svg">
  <!-- more tags here -->
</svg>

命名空间声明由 xmlns 参数提供。该参数表示 <svg> 元素及其子元素属于具有命名空间名称 http://www.w3.org/2000/svg 的任何 XML 方言,当然,这是 SVG。请注意,命名空间声明只在根元素上提供一次(如果省略则为隐式)。该声明定义了 *默认* 命名空间,因此用户代理知道 <svg> 元素的所有后代也属于同一个命名空间。用户代理检查它们是否识别命名空间名称,以确定它们是否知道如何处理标记。

请注意,命名空间名称只是字符串,因此 SVG 命名空间名称看起来像 URI 并不重要。URI 通常被使用,因为它们是唯一的,但目的不是“链接”到某个地方。(事实上,URI 使用得如此频繁,以至于“命名空间 URI”这个词通常被用来代替“命名空间名称”。)

重新声明默认命名空间

如果根元素的所有后代也被定义为在默认命名空间中,那么如何混合来自另一个命名空间的内容?要将 SVG 命名空间包含在 HTML 中,您需要包含 <svg>。在 XML 中,您需要声明一个命名空间。下面是一个简短的示例。

xml
<report xmlns="https://www.acme.org/reports">
  <title>Some stats</title>
  <summary>...</summary>
  <statTable xmlns="https://www.acme.org/tables">
    <content>...</content>
    <!-- redeclaring root's default namespace -->
    <summary xmlns="https://www.acme.org/reports">...</summary>
  </statTable>
</report>

在这个例子中,根 <report> 元素上的 xmlns 属性将默认命名空间声明为 https://www.acme.org/reportsreports。因此,它及其所有子元素被用户代理解释为属于 reports,除了 <content> 元素,该元素存在于 https://www.acme.org/tablestables 命名空间中。<summary> 元素有自己的 xmlns 参数,通过重新声明 reports 命名空间,这告诉用户代理 <summary> 元素及其后代(除非它们也重新声明了替代命名空间)属于 reports

对于 HTML,http://www.w3.org/1999/xhtml 是隐含的命名空间。对于 SVG,它是 http://www.w3.org/2000/svg。MathML 是 http://www.w3.org/1998/Math/MathML

声明命名空间前缀

XML 方言不仅定义了自己的元素,而且还声明了自己的参数。

默认情况下,参数根本没有命名空间。它们只被认为是唯一的,因为它们出现在本身具有唯一名称的元素上。但是,有时有必要定义参数,以便它们可以在许多不同的元素上重复使用,并且仍然被认为是同一个参数,独立于它们所使用的元素。一个很好的例子是 XLink 规范定义的 href 参数。此参数通常被其他 XML 方言用作链接到外部资源的一种手段。但是,如何告诉用户代理参数属于哪个方言,在这种情况下是 XLink?请考虑以下示例。

xml
<svg
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink">
  <script xlink:href="cool-script.js" type="text/javascript" />
</svg>

此示例具有相当不寻常的 xmlns:xlink 参数。正如您可能从第一个 xmlns 部分猜到的,这是一个命名空间声明。但是,它不是设置默认命名空间,而是为“命名空间前缀”设置命名空间。在本例中,我们选择使用前缀 xlink(第二部分),因为该前缀将用于告诉用户代理关于属于 XLink 的属性的信息。

顾名思义,命名空间前缀用于对参数和元素名称进行前缀。这是通过在参数名称之前加上命名空间前缀和冒号来完成的,如上面的 <script> 元素所示。这告诉用户代理,特定参数属于分配给命名空间前缀(XLink)的命名空间,并且是可以在其他元素上使用相同含义的参数。

请注意,在 XML 中,使用未绑定到命名空间名称的前缀是一个 XML 错误。示例中 xmlns:xlink 参数创建的绑定是必需的,这样 xlink:href 参数才不会导致错误。此 XLink 参数也经常在 SVG 的 <a><use><image> 元素中使用,因此最好始终在您的文档中包含 XLink 声明。

顺便说一下,了解命名空间前缀也可以用于元素名称很有用。这告诉用户代理特定元素(但这次不包括其子元素!)属于分配给该前缀的命名空间。如果您遇到以下示例中的标记,了解这一点将避免一些困惑

xml
<html
  lang="en"
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:svg="http://www.w3.org/2000/svg">
  <body>
    <h1>SVG embedded inline in XHTML</h1>
    <svg:svg width="300px" height="200px">
      <svg:circle cx="150" cy="100" r="50" fill="#ff0000" />
    </svg:svg>
  </body>
</html>

注意:这是一个 XHTML 文件,而不是 HTML 文件。XML 命名空间在 HTML 中无效。要尝试此示例,您必须将文件保存为 .xhtml

请注意,由于使用命名空间前缀用于 <svg:svg> 元素及其子元素 <svg:circle>,因此无需重新声明默认命名空间。一般来说,重新声明默认命名空间比以这种方式为大量元素添加前缀更好。

命名空间 XML 中的脚本

命名空间会影响标记和脚本(甚至 CSS)。如果您为命名空间 XML(如 SVG)编写脚本,请继续阅读。

DOM 级别 1 建议是在 XML 中的原始命名空间 建议发布之前创建的;因此,DOM1 不支持命名空间。这对命名空间 XML(如 SVG)造成了问题。为了解决这些问题,DOM 级别 2 核心 添加了所有适用 DOM 级别 1 方法的命名空间感知等效项。在对 SVG 进行脚本编写时,使用 命名空间感知方法 非常重要。下表列出了不应在 SVG 中使用的 DOM1 方法,以及应改用其等效的 DOM2 方法。

DOM1(不要使用) DOM2(改用这些!)
createAttribute() createAttributeNS()
createElement() createElementNS()
getAttributeNode() getAttributeNodeNS()
getAttribute() getAttributeNS()
getElementsByTagName() getElementsByTagNameNS()(也添加到 Element
getNamedItem() getNamedItemNS()
hasAttribute() hasAttributeNS()
removeAttribute() removeAttributeNS()
removeNamedItem() removeNamedItemNS()
setAttribute() setAttributeNS()
setAttributeNode() setAttributeNodeNS()
setNamedItem() setNamedItemNS()

所有 DOM2 命名空间感知方法的第一个参数必须是所讨论元素或参数的命名空间名称(也称为命名空间 URI)。对于 SVG 元素,它是 http://www.w3.org/2000/svg。但是请注意:XML 中的命名空间 1.1 建议指出,没有前缀的参数的命名空间名称没有值。换句话说,尽管参数属于元素的命名空间,但您不使用标签的命名空间名称。相反,对于无限定(无前缀)参数,您必须使用 null 作为命名空间名称。因此,要使用 document.createElementNS() 创建 SVG rect 元素,您必须编写

js
document.createElementNS("http://www.w3.org/2000/svg", "rect");

但要检索 SVG rect 元素上的 x 参数的值,您必须编写

js
rect.getAttributeNS(null, "x");

请注意,对于具有命名空间前缀的参数(不属于与元素相同的 XML 方言的参数)并非如此。例如 xlink:href 等参数需要分配给该前缀的命名空间名称(对于 XLink,为 http://www.w3.org/1999/xlink)。因此,要获取 SVG 中 <a> 元素的 xlink:href 参数的值,您将编写

js
elt.getAttributeNS("http://www.w3.org/1999/xlink", "href");

对于设置具有命名空间的参数,建议(但不是必需的)您还在第二个参数中包含其前缀,以便 DOM 以后可以更轻松地转换回 XML(例如,如果您要将其发送回服务器)。例如

js
elt.setAttributeNS(
  "http://www.w3.org/1999/xlink",
  "xlink:href",
  "otherdoc.svg",
);

最后一个示例演示了如何使用 JavaScript 动态创建 <image> 元素

js
const SVG_NS = "http://www.w3.org/2000/svg";
const XLink_NS = "http://www.w3.org/1999/xlink";
const image = document.createElementNS(SVG_NS, "image");
image.setAttributeNS(null, "width", "100");
image.setAttributeNS(null, "height", "100");
image.setAttributeNS(XLink_NS, "xlink:href", "flower.png");

结论

对于 SVG、HTML 和 MathML,命名空间是隐含的,因此是可选的。需要为 XML 文件声明命名空间。如果不这样做,用户代理将无法识别内容,并将显示 XML 标记或通知用户 XML 中存在错误。

在编写 SVG 时,使用包含所有常用命名空间声明的模板在创建新文件时非常有用。如果您还没有,请从以下代码开始创建模板

xml
<svg
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"></svg>

即使您在特定文档中没有使用所有这些命名空间,包含命名空间声明也没有什么坏处。如果您最终在以后添加来自未使用命名空间之一的内容,它可能会避免一些恼人的错误。

完整的示例

有关完整示例,请参见 SVG:命名空间速成课程:示例