探索 DTD 在 XML 中的作用及解析:深入理解文件型別定義

小万哥丶發表於2024-04-26

DTD 是文件型別定義(Document Type Definition)的縮寫。DTD 定義了 XML 文件的結構以及合法的元素和屬性。

為什麼使用 DTD

透過使用 DTD,獨立的團體可以就資料交換的標準 DTD 達成一致。

應用程式可以使用 DTD 來驗證 XML 資料的有效性。

內部 DTD 宣告

如果 DTD 在 XML 檔案內宣告,它必須包裹在 <DOCTYPE> 定義內:

帶有內部 DTD 的 XML 文件

<?xml version="1.0"?>
<!DOCTYPE note [
<!ELEMENT note (to, from, heading, body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend</body>
</note>

在 XML 檔案中,選擇"view source" 以檢視 DTD。

上述 DTD 的解釋如下:

  • <!DOCTYPE note> 定義該文件的根元素為 note
  • <!ELEMENT note> 定義 note 元素必須包含四個元素:"to, from, heading, body"
  • <!ELEMENT to> 定義 to 元素的型別為 "#PCDATA"
  • <!ELEMENT from> 定義 from 元素的型別為 "#PCDATA"
  • <!ELEMENT heading> 定義 heading 元素的型別為 "#PCDATA"
  • <!ELEMENT body> 定義 body 元素的型別為 "#PCDATA"

外部 DTD 宣告

如果 DTD 在外部檔案中宣告,<!DOCTYPE> 定義必須包含對 DTD 檔案的引用:

帶有對外部 DTD 引用的 XML 文件

<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>

以下是包含 DTD 的檔案 "note.dtd" 的內容:

<!ELEMENT note (to, from, heading, body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>

DTD - XML 構建模組

XML 和 HTML 文件的主要構建模組是元素

XML 文件的構建模組

從 DTD 的角度來看,所有 XML 文件都由以下構建模組組成:

  • 元素
  • 屬性
  • 實體
  • PCDATA
  • CDATA

元素

元素是 XML 和 HTML 文件的主要構建模組。

HTML 元素的示例包括 "body" 和 "table"。XML 元素的示例可能是 "note" 和 "message"。元素可以包含文字、其他元素或為空。空的 HTML 元素的示例包括 "hr"、 "br" 和 "img"。

示例

<body>some text</body>

<message>some text</message>

屬性

屬性提供有關元素的額外資訊。

屬性始終位於元素的開始標記內。屬性始終以名稱/值對的形式出現。以下是具有有關原始檔的附加資訊的 "img" 元素的示例

<img src="computer.gif" />

實體

一些字元在 XML 中具有特殊含義,例如小於號(<),它定義了 XML 標記的開始。

大多數人都知道 HTML 實體: "&nbsp;"。這個 "no-breaking-space" 實體用於在 HTML 文件中插入額外的空格。實體在 XML 解析器解析文件時會被展開。

以下實體在 XML 中是預定義的:

  • &lt; 代表 <
  • &gt; 代表 >
  • &amp; 代表 &
  • &quot; 代表 "
  • &apos; 代表 '

PCDATA

PCDATA 表示解析的字元資料。

將字元資料視為 XML 元素的開始標記和結束標記之間找到的文字。

PCDATA 是解析器將解析的文字。解析器將檢查文字中的實體和標記。

文字內的標記將被視為標記,並且實體將被展開。

但是,解析的字元資料不應包含任何&、<或>字元;這些需要用分別表示為 &amp; &lt;&gt; 實體。

CDATA

CDATA 表示字元資料。

CDATA 是解析器將不解析的文字。文字內的標記將不被視為標記,並且實體將不被展開。

DTD - 元素

在 DTD 中,元素透過 ELEMENT 宣告進行宣告

宣告元素

在 DTD 中,XML 元素的宣告具有以下語法:

<!ELEMENT element-name category>

或者

<!ELEMENT element-name (element-content)>

空元素

空元素透過 category 關鍵字 EMPTY 進行宣告:

<!ELEMENT element-name EMPTY>

示例

<!ELEMENT br EMPTY>

XML 示例

<br />

具有解析字元資料的元素

僅包含解析字元資料的元素在括號內使用 #PCDATA 進行宣告:

<!ELEMENT element-name (#PCDATA)>

示例

<!ELEMENT from (#PCDATA)>

具有任何內容的元素

使用 category 關鍵字 ANY 宣告的元素可以包含任意可解析的資料組合:

<!ELEMENT element-name ANY>

示例

<!ELEMENT note ANY>

具有子元素(序列)的元素

具有一個或多個子元素的元素透過在括號內宣告子元素的名稱進行宣告:

<!ELEMENT element-name (child1)>

或者

<!ELEMENT element-name (child1,child2,...)>

示例

<!ELEMENT note (to,from,heading,body)>

當子元素按逗號分隔在序列中宣告時,子元素必須按相同的順序出現在文件中。在完整宣告中,子元素也必須被宣告,並且子元素也可以有子元素。 "note" 元素的完整宣告如下:

<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>

宣告元素的僅出現一次

<!ELEMENT element-name (child-name)>

示例

<!ELEMENT note (message)>

上面的示例宣告瞭子元素 "message" 必須在 "note" 元素內出現一次,且僅一次。

宣告元素至少出現一次

<!ELEMENT element-name (child-name+)>

示例

<!ELEMENT note (message+)>

上面示例中的+號表示子元素 "message" 必須在 "note" 元素內出現一次或多次。

宣告元素出現零次或更多次

<!ELEMENT element-name (child-name*)>

示例

<!ELEMENT note (message*)>

上面示例中的*號表示子元素 "message" 可以在 "note" 元素內出現零次或更多次。

宣告元素出現零次或一次

<!ELEMENT element-name (child-name?)>

示例

<!ELEMENT note (message?)>

上面示例中的?號表示子元素 "message" 可以在 "note" 元素內出現零次或一次。

宣告要麼/或內容

<!ELEMENT note (to,from,header,(message|body))>

上面的示例宣告瞭 "note" 元素必須包含一個 "to" 元素、一個 "from" 元素、一個 "header" 元素,以及一個 "message" 或 "body" 元素。

宣告混合內容

<!ELEMENT note (#PCDATA|to|from|header|message)*>

上面的示例宣告瞭 "note" 元素可以包含零個或多個解析字元資料、"to"、"from"、"header" 或 "message" 元素的出現。

DTD - 屬性

在 DTD 中,使用 ATTLIST 宣告來宣告屬性

宣告屬性

屬性宣告具有以下語法:

<!ATTLIST element-name attribute-name attribute-type attribute-value>

DTD 示例

<!ATTLIST payment type CDATA "check">

XML 示例

<payment type="check" />

attribute-type 可以是以下之一:

  • CDATA:值是字元資料
  • (en1|en2|..):值必須是列舉列表中的一個
  • ID:值是唯一識別符號
  • IDREF:值是另一個元素的識別符號
  • IDREFS:值是其他識別符號的列表
  • NMTOKEN:值是有效的 XML 名稱
  • NMTOKENS:值是有效的 XML 名稱的列表
  • ENTITY:值是實體
  • ENTITIES:值是實體的列表
  • NOTATION:值是符號的名稱
  • xml::值是預定義的 xml 值

attribute-value 可以是以下之一:

  • value:屬性的預設值
  • #REQUIRED:屬性是必需的
  • #IMPLIED:屬性是可選的
  • #FIXED value:屬性值是固定的

預設屬性值

<!ELEMENT square EMPTY>
<!ATTLIST square width CDATA "0">

有效的 XML

<square width="100" />

在上面的示例中,“square”元素被定義為一個帶有型別 CDATA 的空元素。如果未指定寬度,則其預設值為 0。

REQUIRED

語法

<!ATTLIST element-name attribute-name attribute-type #REQUIRED>

示例

<!ATTLIST person number CDATA #REQUIRED>

有效的 XML

<person number="5677" />

無效的 XML

<person />

如果沒有預設值的選項,但仍希望強制屬性存在,請使用 #REQUIRED 關鍵字。

IMPLIED

語法:

<!ATTLIST element-name attribute-name attribute-type #IMPLIED>

示例

<!ATTLIST contact fax CDATA #IMPLIED>

有效的 XML:

<contact fax="555-667788" />

有效的 XML:

<contact />

如果不想強制作者包含屬性,並且沒有預設值的選項,請使用 #IMPLIED 關鍵字。

FIXED

語法:

<!ATTLIST element-name attribute-name attribute-type #FIXED "value">

示例

<!ATTLIST sender company CDATA #FIXED "Microsoft">

有效的 XML:

<sender company="Microsoft" />

無效的 XML:

<sender company="W3Schools" />

當希望屬性具有固定值而不允許作者更改時,請使用 #FIXED 關鍵字。如果作者包含其他值,XML 解析器將返回錯誤。

列舉屬性值

語法

<!ATTLIST element-name attribute-name (en1|en2|..) default-value>

示例

<!ATTLIST payment type (check|cash) "cash">

XML 示例

<payment type="check" />

<payment type="cash" />

當希望屬性值是固定一組合法值之一時,請使用列舉屬性值。

XML 元素與屬性

在 XML 中,沒有規定何時使用屬性,何時使用子元素。

元素與屬性的使用

資料可以儲存在子元素中,也可以儲存在屬性中。

請看以下示例

<person sex="female">
  <firstname>Anna</firstname>
  <lastname>Smith</lastname>
</person>

<person>
  <sex>female</sex>
  <firstname>Anna</firstname>
  <lastname>Smith</lastname>
</person>

在第一個示例中,sex 是一個屬性。在最後一個示例中,sex 是一個子元素。這兩個示例提供了相同的資訊。

在何時使用屬性以及何時使用子元素方面,沒有具體的規則。根據我的經驗,在 HTML 中使用屬性很方便,但在 XML 中應該儘量避免使用。如果資訊看起來像是資料,請使用子元素

以下三個 XML 文件包含完全相同的資訊:

  1. 使用了一個 date 屬性:
<note date="12/11/2002">
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>
  1. 使用了一個 date 元素:
<note>
  <date>12/11/2002</date>
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>
  1. 使用了擴充套件的 date 元素(這是我喜歡的):
<note>
  <date>
    <day>12</day>
    <month>11</month>
    <year>2002</year>
  </date>
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>

避免使用屬性?

是否應該避免使用屬性?

一些使用屬性的問題包括:

  • 屬性不能包含多個值(子元素可以)
  • 屬性不容易擴充套件(用於未來更改)
  • 屬性不能描述結構(子元素可以)
  • 屬性更難以透過程式程式碼進行操作
  • 屬性值不容易與 DTD 進行測試

如果將屬性用作資料的容器,最終會得到難以閱讀和維護的文件。儘量使用元素來描述資料。僅在提供與資料無關的資訊時使用屬性。

不要像這樣使用 XML(這不是 XML 的正確用法)

<note day="12" month="11" year="2002"
to="Tove" from="Jani" heading="Reminder"
body="Don't forget me this weekend!">
</note>

關於屬性規則有一個例外:

有時會為元素分配 ID 引用。這些 ID 引用可以用於訪問 XML 元素,方式類似於 HTML 中的 NAME 或 ID 屬性。這個例子演示了這一點:

<messages>
<note id="p501">
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>

<note id="p502">
  <to>Jani</to>
  <from>Tove</from>
  <heading>Re: Reminder</heading>
  <body>I will not!</body>
</note>
</messages>

這些示例中的 ID 只是一個計數器或唯一識別符號,用於識別 XML 檔案中不同的 note,並不是 note 資料的一部分。

這裡想說的是,後設資料(關於資料的資料)應該儲存為屬性,而資料本身應該儲存為元素。

實體宣告

實體(Entity)被用來定義對特殊字元的快捷方式。實體可以宣告為內部或外部。

內部實體宣告

語法

<!ENTITY entity-name "entity-value">

示例

DTD示例

<!ENTITY writer "Donald Duck.">
<!ENTITY copyright "Copyright W3Schools.">

XML示例

<author>&writer;&copyright;</author>

注意:一個實體由三部分組成:一個 & 符號、一個實體名和一個分號。

外部實體宣告

語法

<!ENTITY entity-name SYSTEM "URI/URL">

XML示例

<author>&writer;&copyright;</author>

DTD示例

電視節目表DTD

<!DOCTYPE TVSCHEDULE [

<!ELEMENT TVSCHEDULE (CHANNEL+)>
<!ELEMENT CHANNEL (BANNER,DAY+)>
<!ELEMENT BANNER (#PCDATA)>
<!ELEMENT DAY (DATE,(HOLIDAY|PROGRAMSLOT+)+)>
<!ELEMENT HOLIDAY (#PCDATA)>
<!ELEMENT DATE (#PCDATA)>
<!ELEMENT PROGRAMSLOT (TIME,TITLE,DESCRIPTION?)>
<!ELEMENT TIME (#PCDATA)>
<!ELEMENT TITLE (#PCDATA)> 
<!ELEMENT DESCRIPTION (#PCDATA)>

<!ATTLIST TVSCHEDULE NAME CDATA #REQUIRED>
<!ATTLIST CHANNEL CHAN CDATA #REQUIRED>
<!ATTLIST PROGRAMSLOT VTR CDATA #IMPLIED>
<!ATTLIST TITLE RATING CDATA #IMPLIED>
<!ATTLIST TITLE LANGUAGE CDATA #IMPLIED>
]>

報紙文章DTD

<!DOCTYPE NEWSPAPER [

<!ELEMENT NEWSPAPER (ARTICLE+)>
<!ELEMENT ARTICLE (HEADLINE,BYLINE,LEAD,BODY,NOTES)>
<!ELEMENT HEADLINE (#PCDATA)>
<!ELEMENT BYLINE (#PCDATA)>
<!ELEMENT LEAD (#PCDATA)>
<!ELEMENT BODY (#PCDATA)>
<!ELEMENT NOTES (#PCDATA)>

<!ATTLIST ARTICLE AUTHOR CDATA #REQUIRED>
<!ATTLIST ARTICLE EDITOR CDATA #IMPLIED>
<!ATTLIST ARTICLE DATE CDATA #IMPLIED>
<!ATTLIST ARTICLE EDITION CDATA #IMPLIED>

<!ENTITY NEWSPAPER "Vervet Logic Times">
<!ENTITY PUBLISHER "Vervet Logic Press">
<!ENTITY COPYRIGHT "Copyright 1998 Vervet Logic Press">

]>

產品目錄DTD

<!DOCTYPE CATALOG [

<!ENTITY AUTHOR "John Doe">
<!ENTITY COMPANY "JD Power Tools, Inc.">
<!ENTITY EMAIL "jd@jd-tools.com">

<!ELEMENT CATALOG (PRODUCT+)>

<!ELEMENT PRODUCT
(SPECIFICATIONS+,OPTIONS?,PRICE+,NOTES?)>
<!ATTLIST PRODUCT
NAME CDATA #IMPLIED
CATEGORY (HandTool|Table|Shop-Professional) "HandTool"
PARTNUM CDATA #IMPLIED
PLANT (Pittsburgh|Milwaukee|Chicago) "Chicago"
INVENTORY (InStock|Backordered|Discontinued) "InStock">

<!ELEMENT SPECIFICATIONS (#PCDATA)>
<!ATTLIST SPECIFICATIONS
WEIGHT CDATA #IMPLIED
POWER CDATA #IMPLIED>

<!ELEMENT OPTIONS (#PCDATA)>
<!ATTLIST OPTIONS
FINISH (Metal|Polished|Matte) "Matte"
ADAPTER (Included|Optional|NotApplicable) "Included"
CASE (HardShell|Soft|NotApplicable) "HardShell">

<!ELEMENT PRICE (#PCDATA)>
<!ATTLIST PRICE
MSRP CDATA #IMPLIED
WHOLESALE CDATA #IMPLIED
STREET CDATA #IMPLIED
SHIPPING CDATA #IMPLIED>

<!ELEMENT NOTES (#PCDATA)>

]>

## 最後

為了方便其他裝置和平臺的小夥伴觀看往期文章:

微信公眾號搜尋:`Let us Coding`,關注後即可獲取最新文章推送

看完如果覺得有幫助,歡迎點贊、收藏、關注

相關文章