命名空间速成课

作为一种XML方言,SVG是带有命名空间的。如果您计划编写SVG内容,理解命名空间的概念以及它们如何使用是很重要的。命名空间对于支持多种XML方言的用户代理至关重要;浏览器必须非常严格。现在花时间理解命名空间将为您省去未来的麻烦。

Background

各种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声明(通常)与单一内容类型文件中的内容匹配仅仅是巧合。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”一词通常取代了“命名空间名称”)。

重新声明默认命名空间

如果根元素的所有后代也都定义在默认命名空间中,那么如何混合来自其他命名空间的内容呢?要在HTML中包含SVG命名空间,您可以包含<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/reports,或reports。因此,它及其所有子元素都被用户代理解释为属于reports,除了<content>元素,该元素存在于https://www.acme.org/tables,或tables命名空间中。<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="red" />
    </svg:svg>
  </body>
</html>

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

请注意,由于<svg:svg>元素及其子元素<svg:circle>使用了命名空间前缀,因此无需重新声明默认命名空间。通常,最好重新声明默认命名空间,而不是以这种方式为许多元素添加前缀。

命名空间XML中的脚本

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

DOM Level 1建议是在XML中的原始命名空间建议发布之前创建的;因此,DOM1不感知命名空间。这会给SVG等命名空间XML带来问题。为了解决这些问题,DOM Level 2 Core添加了所有适用DOM Level 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",
  "other-doc.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:命名空间速成课程:示例