名稱空間速成課

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