名稱空間速成課程

作為一種 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:名稱空間速成課程:示例