文档类型定义和合法性(2)
8.5.3 子元素列表
由于SEASON元素被聲明為可以接受任何元素作為子元素,因而可以接受各種各樣的元素。當遇到那些多多少少有些非結構化的文本,如雜志文章時,這種情況就很有用。這時段落、副欄、項目列表、序號列表、圖形、照片以及子標題可出現在文檔的任意位置。然而,有時可能想對數據的安排上多實行些規則和控制。例如,可能會要求每一個LEAGUE元素有一個LEAGUE_NAME子元素,而每個PLAYER元素要有一個GIVEN_NAME和SURNAME子元素,并且GIVEN_NAME要放在SURNAME之前。
為了聲明LEAGUE元素必須有一個名稱,只要聲明LEAGUE_NAME元素,然后在LEAGUE聲明后的括號內加入LEAGUE_NAME,如下面這樣:
<!ELEMENT LEAGUE (LEAGUE_NAME)>
<!ELEMENT LEAGUE_NAME (#PCDATA)>
每個元素只能在其<!ELEMENT>內聲明一次,即使它以其他<!ELEMENT>聲明的子元素出現也一樣。這里,我把LEAGUE_NAME聲明放在引用它的LEAGUE聲明之后,這沒有關系。XML允許這一類提前引用。只要聲明全部包含在DTD中,元素標記出現的順序無關緊要。
可以向文檔中添加這兩項聲明,然后在SEASON元素中包括LEAGUE和LEAGUE_NAME元素。如清單8-8所示。圖8-8是顯示出來的文檔。
清單8-8:有兩個LEAGUE子元素的SEASON元素
<?xml version="1.0" standalone="yes"?>
<?xml-stylesheet type="text/css" href="greeting.css"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME)>
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!ELEMENT SEASON ANY>
]>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>American League</LEAGUE_NAME>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>National League</LEAGUE_NAME>
</LEAGUE>
</SEASON>
圖8-8 包含樣式單、YEAR元素和兩個LEAGUE子元素的合法的文檔
8.5.4 序列
讓我們限制一下SEASON元素。一個SEASON元素包含正好一個YEAR元素和其后的兩個LEAGUE子元素。不把SEASON元素聲明為可以包含ANY元素,我們在SEASON元素聲明中包括這三個子元素,用括號括起來并用逗號分隔開,如下所示:
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
用逗號隔開的一系列子元素稱為一個序列。利用這一聲明,每個合法的SEASON元素必須包含正好一個YEAR元素,后面正好是兩個LEAGUE元素,沒有別的。整個文檔類型定義現在看上去是下面的樣子:
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME)>
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
]>
清單8-8所列的文檔部分確實符合這項DTD的規定,因為它的SEASON元素包含一個YEAR子元素,后接兩個LEAGUE子元素,再沒有別的。但是,如果文檔只包括一個SEASON元素,那么這個文檔盡管結構完整,也將是非法的。同樣,如果LEAGUE在YEAR之前而不是在其后,或者如果LEAGUE有YEAR子元素,或者文檔在其他任何方面不符合DTD,那么文檔就是不合法的,合法性檢查程序將拒絕這樣的文檔。
可直接將此種技術推廣到DIVISION元素。每個LEAGUE有一個LEAGUE_NAME和三個DIVISION子元素。例如:
<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>
8.5.5 一個或多個子元素
每個DIVISION有一個DIVISION_NAME和四到六個TEAM子元素。指定DIVISION_NAME很容易,方法如下:
<!ELEMENT DIVISION (DIVISION_NAME)>
<!ELEMENT DIVISION_NAME (#PCDATA)>
但是,TEAM子元素就很棘手。指明DIVISION元素有四個TEAM子元素很容易,如下所示:
<!ELEMENT DIVISION (DIVISION_NAME, TEAM, TEAM, TEAM, TEAM)>
五個和六個也不難。但是您怎樣說明有四到六個TEAM子元素呢?實際上,XML沒有提供實現的簡單方法。但是可以在子元素清單的元素名后放一個加號(+)來說明有一個或多個子元素,例如:
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
這就是說一個DIVISION元素必須包含一個DIVISION_NAME子元素,后接一個或多個TEAM子元素。
說明DIVISION元素有四到六個TEAM元素,而不是三到七個,這就難了。由于非常復雜,實際上很少有人使用。當讀完本章時,看一看您是否已經想出怎樣做了。
8.5.6 零或多個子元素
每個TEAM要包含一個TEAM_CITY,一個TEAM_NAME和不確定數目的PLAYER元素。實際上,棒球隊至少要九名球員。但是,本書的很多例子中由于篇幅的原因而沒有列出球員。因而,我們要指明一個TEAM元素可包含零或多個PLAYER子元素。在子元素清單中在元素名上附加一個星號(*)來實現這一目的。例如:
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>
<!ELEMENT TEAM_CITY (#PCDATA)>
<!ELEMENT TEAM_NAME (#PCDATA)>
8.5.7 零或一個子元素
文檔中出現的最后的元素是PLAYER子元素。它們全部是只包含文本的簡單元素。下面是它們的聲明:
<!ELEMENT SURNAME (#PCDATA)>
<!ELEMENT GIVEN_NAME (#PCDATA)>
<!ELEMENT POSITION (#PCDATA)>
<!ELEMENT GAMES (#PCDATA)>
<!ELEMENT GAMES_STARTED (#PCDATA)>
<!ELEMENT AT_BATS (#PCDATA)>
<!ELEMENT RUNS (#PCDATA)>
<!ELEMENT HITS (#PCDATA)>
<!ELEMENT DOUBLES (#PCDATA)>
<!ELEMENT TRIPLES (#PCDATA)>
<!ELEMENT HOME_RUNS (#PCDATA)>
<!ELEMENT RBI (#PCDATA)>
<!ELEMENT STEALS (#PCDATA)>
<!ELEMENT CAUGHT_STEALING (#PCDATA)>
<!ELEMENT SACRIFICE_ HITS (#PCDATA)>
<!ELEMENT SACRIFICE_FLIES (#PCDATA)>
<!ELEMENT ERRORS (#PCDATA)>
<!ELEMENT WALKS (#PCDATA)>
<!ELEMENT STRUCK_OUT (#PCDATA)>
<!ELEMENT HIT_BY_PITCH (#PCDATA)>
<!ELEMENT COMPLETE_GAMES (#PCDATA)>
<!ELEMENT SHUT_OUTS (#PCDATA)>
<!ELEMENT ERA (#PCDATA)>
<!ELEMENT INNINGS (#PCDATA)>
<!ELEMENT EARNED_RUNS (#PCDATA)>
<!ELEMENT HIT_BATTER (#PCDATA)>
<!ELEMENT WILD_PITCHES (#PCDATA)>
<!ELEMENT BALK (#PCDATA)>
<!ELEMENT WALKED_BATTER (#PCDATA)>
<!ELEMENT WINS (#PCDATA)>
<!ELEMENT LOSSES (#PCDATA)>
<!ELEMENT SAVES (#PCDATA)>
<!ELEMENT COMPLETE_GAMES (#PCDATA)>
<!ELEMENT STRUCK_OUT_BATTER (#PCDATA)>
現在我們可以編寫PLAYER的元素聲明了。所有球員有一個GIVEN_NAME、一個SURNAME、一個POSITION、一個GAMES。我們可聲明每個PLAYER元素有一個AT_BATS、RUNS、HITS等等。但是,對于沒有擊球的投球手列出零得分是否準確還不敢確定。因為這可能出現這樣一種情況,就是在開始計算平均擊球數等問題時會導致被零除的錯誤。如果某一特定的元素不適合于給定的球員,或沒有這一元素,那么就應該從該球員信息中忽略這一元素的統計。對于給定的球員我們不允許多于一個這樣的元素。因而,我們就需要給定類型的零個或一個元素。在子元素列表后面附加一個問號(?)可表明這一點,如下所示:
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION,
GAMES,GAMES_STARTED,AT_BATS?,RUNS?,HITS?,DOUBLES?,
TRIPLES?, HOME_RUNS?, RBI?, STEALS?, CAUGHT_STEALING?,
SACRIFICE_ HITS?, SACRIFICE_FLIES?,ERRORS?, WALKS?,
STRUCK_OUT?, HIT_BY_PITCH ?, WINS?, LOSSES?, SAVES?,
COMPLETE_GAMES?,SHUT_OUTS?,ERA?,INNINGS EARNED_RUNS?,HIT_BATTER?,WILD_PITCHES?,
BALK?,WALKED_BATTER?,STRUCK_OUT_BATTER?)
>
這就是說每個PLAYER元素有一個GIVEN_NAME、SURNAME、POSITION、GAMES和GAMES_STARTED子元素。而且,每名球員可能有或可能沒有AT_BATS、RUNS、HITS、DOUBLES、TRIPLES、HOME_RUNS、RBI、STEALS、CAUGHT_STEALING、SACRIFICE_HITS、SACRIFICE_FLIES、ERRORS、WALKS、STRUCK_OUT和HIT_BY_PITCH。
8.5.8 完整的文檔和DTD
我們現在有了棒球統計的完整的DTD。這一DTD連同清單8-4中的文檔部分一起,列在清單8-9中。
清單8-9只包括一個隊和九名球員。在本書后附CD-ROM上的examples/baseball/1998validstats.xml目錄下可找到1998年主要聯賽球隊和隊員的統計文檔。
清單8-9:一份合法的棒球統計文檔和DTD
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!ELEMENT DIVISION_NAME (#PCDATA)>
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>
<!ELEMENT TEAM_CITY (#PCDATA)>
<!ELEMENT TEAM_NAME (#PCDATA)>
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION, GAMES,GAMES_STARTED, WINS?, LOSSES?, SAVES?,
AT_BATS?, RUNS?, HITS?, DOUBLES?, TRIPLES?, HOME_RUNS?,
RBI?, STEALS?, CAUGHT_STEALING?, SACRIFICE_HITS?,
SACRIFICE_FLIES?, ERRORS?, WALKS?, STRUCK_OUT?,
HIT_BY_PITCH?, COMPLETE_GAMES?, SHUT_OUTS?, ERA?,
INNINGS?,EARNED_RUNS?, HIT_BATTER?, WILD_PITCHES?,
BALK?,WALKED_BATTER?, STRUCK_OUT_BATTER?)
>
<!ELEMENT SURNAME (#PCDATA)>
<!ELEMENT GIVEN_NAME (#PCDATA)>
<!ELEMENT POSITION (#PCDATA)>
<!ELEMENT GAMES (#PCDATA)>
<!ELEMENT GAMES_STARTED (#PCDATA)>
<!ELEMENT COMPLETE_GAMES (#PCDATA)>
<!ELEMENT WINS (#PCDATA)>
<!ELEMENT LOSSES (#PCDATA)>
<!ELEMENT SAVES (#PCDATA)>
<!ELEMENT AT_BATS (#PCDATA)>
<!ELEMENT RUNS (#PCDATA)>
<!ELEMENT HITS (#PCDATA)>
<!ELEMENT DOUBLES (#PCDATA)>
<!ELEMENT TRIPLES (#PCDATA)>
<!ELEMENT HOME_RUNS (#PCDATA)>
<!ELEMENT RBI (#PCDATA)>
<!ELEMENT STEALS (#PCDATA)>
<!ELEMENT CAUGHT_STEALING (#PCDATA)>
<!ELEMENT SACRIFICE_HITS (#PCDATA)>
<!ELEMENT SACRIFICE_FLIES (#PCDATA)>
<!ELEMENT ERRORS (#PCDATA)>
<!ELEMENT WALKS (#PCDATA)>
<!ELEMENT STRUCK_OUT (#PCDATA)>
<!ELEMENT HIT_BY_PITCH (#PCDATA)>
<!ELEMENT SHUT_OUTS (#PCDATA)>
<!ELEMENT ERA (#PCDATA)>
<!ELEMENT INNINGS (#PCDATA)>
<!ELEMENT HOME_RUNS_AGAINST (#PCDATA)>
<!ELEMENT RUNS_AGAINST (#PCDATA)>
<!ELEMENT EARNED_RUNS (#PCDATA)>
<!ELEMENT HIT_BATTER (#PCDATA)>
<!ELEMENT WILD_PITCHES (#PCDATA)>
<!ELEMENT BALK (#PCDATA)>
<!ELEMENT WALKED_BATTER (#PCDATA)>
<!ELEMENT STRUCK_OUT_BATTER (#PCDATA)>
]>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Florida</TEAM_CITY>
<TEAM_NAME>Marlins</TEAM_NAME>
<PLAYER>
<GIVEN_NAME>Eric</GIVEN_NAME>
<SURNAME>Ludwick</SURNAME>
<POSITION>Starting Pitcher</POSITION>
<GAMES>13</GAMES>
<GAMES_STARTED>6</GAMES_STARTED>
<WINS>1</WINS>
<LOSSES>4</LOSSES>
<SAVES>0</SAVES>
<COMPLETE_GAMES>0</COMPLETE_GAMES>
<SHUT_OUTS>0</SHUT_OUTS>
<ERA>7.44</ERA>
<INNINGS>32.2</INNINGS>
<EARNED_RUNS>31</EARNED_RUNS>
<HIT_BATTER>27</HIT_BATTER>
<WILD_PITCHES>0</WILD_PITCHES>
<BALK>2</BALK>
<WALKED_BATTER>0</WALKED_BATTER>
<STRUCK_OUT_BATTER>17</STRUCK_OUT_BATTER>
</PLAYER>
<PLAYER>
<GIVEN_NAME>Brian</GIVEN_NAME>
<SURNAME>Daubach</SURNAME>
<POSITION>First Base</POSITION>
<GAMES>10</GAMES>
<GAMES_STARTED>3</GAMES_STARTED>
<AT_BATS>15</AT_BATS>
<RUNS>0</RUNS>
<HITS>3</HITS>
<DOUBLES>1</DOUBLES>
<TRIPLES>0</TRIPLES>
<HOME_RUNS>0</HOME_RUNS>
<RBI>3</RBI>
<STEALS>0</STEALS>
<CAUGH T_STEALING>0</CAUGHT_STEALING>
<SACRIFICE_ HITS>0</SACRIFICE_HITS>
<SACRIFICE_FLIES>0</SACRIFICE_FLIES>
<ERRORS>0</ERRORS>
<WALKS>1</WALKS>
<STRUCK_OUT>5</STRUCK_OUT>
<HIT_BY_PITCH>1</HIT_BY_PITCH>
</PLAYER>
</TEAM>
<TEAM>
<TEAM_CITY>Montreal</TEAM_CITY>
<TEAM_NAME>Expos</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Mets</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Philadelphia</TEAM_CITY>
<TEAM_NAME>Phillies</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>Cubs</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
清單8-9不是符合這項DTD的唯一可能的文檔,清單8-10也是一份合法的文檔,因為它按規定的順序包含了需要的所有元素,并且不包含未經聲明的任何元素。這也許是您根據DTD創建的最短的合法文檔。限定因素是這樣的要求,每個SEASON包含兩個LEAGUE子元素,每個LEAGUE子元素包含三個DIVISION子元素,每個DIVISION包含至少一個TEAM子元素。
清單8-10:另外一份符合棒球DTD的合法的XML文檔
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!ELEMENT DIVISION_NAME (#PCDATA)>
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>
<!ELEMENT TEAM_CITY (#PCDATA)>
<!ELEMENT TEAM_NAME (#PCDATA)>
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION, GAMES,
GAMES_STARTED, COMPLETE_GAMES?, WINS?, LOSSES?, SAVES?,
AT_BATS?, RUNS?, HITS?, DOUBLES?, TRIPLES?, HOME_RUNS?,
RBI?, STEALS?, CAUGHT_STEALING?, SACRIFICE_ HITS?,
SACRIFICE_FLIES?, ERRORS?, WALKS?, STRUCK_OUT?,
HIT_BY_PITCH?, COMPLETE_GAMES?, SHUT_OUTS?, ERA?, INNINGS?,
EARNED_RUNS?, HIT_BATTER?, WILD_PITCHES?, BALK?,
WALKED_BATTER?, STRUCK_OUT_BATTER?)
>
<!ELEMENT SURNAME (#PCDATA)>
<!ELEMENT GIVEN_NAME (#PCDATA)>
<!ELEMENT POSITION (#PCDATA)>
<!ELEMENT GAMES (#PCDATA)>
<!ELEMENT GAMES_STARTED (#PCDATA)>
<!ELEMENT COMPLETE_GAMES (#PCDATA)>
<!ELEMENT WINS (#PCDATA)>
<!ELEMENT LOSSES (#PCDATA)>
<!ELEMENT SAVES (#PCDATA)>
<!ELEMENT AT_BATS (#PCDATA)>
<!ELEMENT RUNS (#PCDATA)>
<!ELEMENT HITS (#PCDATA)>
<!ELEMENT DOUBLES (#PCDATA)>
<!ELEMENT TRIPLES (#PCDATA)>
<!ELEMENT HOME_RUNS (#PCDATA)>
<!ELEMENT RBI (#PCDATA)>
<!ELEMENT STEALS (#PCDATA)>
<!ELEMENT CAUGHT_STEALING (#PCDATA)>
<!ELEMENT SACRIFICE_ HITS (#PCDATA)>
<!ELEMENT SACRIFICE_FLIES (#PCDATA)>
<!ELEMENT ERRORS (#PCDATA)>
<!ELEMENT WALKS (#PCDATA)>
<!ELEMENT STRUCK_OUT (#PCDATA)>
<!ELEMENT HIT_BY_PITCH (#PCDATA)>
<!ELEMENT SHUT_OUTS (#PCDATA)>
<!ELEMENT ERA (#PCDATA)>
<!ELEMENT INNINGS (#PCDATA)>
<!ELEMENT HOME_RUNS_AGAINST (#PCDATA)>
<!ELEMENT RUNS_AGAINST (#PCDATA)>
<!ELEMENT EARNED_RUNS (#PCDATA)>
<!ELEMENT HIT_BATTER (#PCDATA)>
<!ELEMENT WILD_PITCHES (#PCDATA)>
<!ELEMENT BALK (#PCDATA)>
<!ELEMENT WALKED_BATTER (#PCDATA)>
<!ELEMENT STRUCK_OUT_BATTER (#PCDATA)>
]>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Atlanta</TEAM_CITY>
<TEAM_NAME>Braves</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Florida</TEAM_CITY>
<TEAM_NAME>Marlins</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Montreal</TEAM_CITY>
<TEAM_NAME>Expos</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Mets</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Philadelphia</TEAM_CITY>
<TEAM_NAME>Phillies</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>Cubs</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East </DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
8.5.9 選擇
通常,一個父元素會有許多子元素。為了指明各子元素必須按順序出現,可將這些子元素用逗號隔開。每個這樣的子元素還可以以問號、加號或星號作后綴,以便調節它在那一位置按順序出現的次數。
到目前為止,已經假定子元素以一定的順序出現或不出現。還可以使DTD更加靈活,如允許文檔作者在給定的地方選擇不同的元素。例如,在一項描述顧客購物的DTD中,結帳方式信息中的每項PAYMENT元素都有CREDIT_CARD子元素或CASH子元素以便提供付款方式的信息。然而,單獨的PAYMENT元素不能同時使用兩者。
在父元素聲明中,可以使用豎線(1)而不是逗號來分開子元素,以便指明文檔作者需要輸入一個或另一個子元素。例如,下面的語句就說明PAYMENT元素必須有CASH或CREDIT_CARD中的一個子元素。
<!ELEMENT PAYMENT (CASH | CREDIT_CARD)>
這種內容規格稱為選擇。當只使用它們當中的一個時就可用豎線分開任意數目的子元素。例如,下面語句說明PAYMENT元素必須有CASH、CREDIT_CARD或CHECK中的一個子元素。
<!ELEMENT PAYMENT (CASH | CREDIT_CARD | CHECK)>
當用括號對元素分組時豎線還會更有用??梢园言亟M合在括號內分組,然后在括號后加星號、問號和加號后綴來指明一定的元素組合會出現零次或多次、零次或一次或者一次或多次。
8.5.10 帶括號的子元素
在父元素聲明中,必須了解有關子元素安排的最后一件事是如何用括號分組元素。每一對括號把數個元素合為一個獨立元素。括號內的元素可以作為獨立元素嵌套在其他括號內。而且,還可以加上加號、逗號或問號后綴。您還可以將這些括號組合成更大的括號組合來構成復雜的結構。這是一項功能強大的技術。
例如,考慮一份由兩個互相可交換的元素組成的清單。這基本上是HTML中定義清單的方法。每項<dt>標記要與一項<dd>標記相匹配。如果用XML來復制這一結構,dl元素的聲明看起來是這樣的:
<!ELEMENT dl (dt , dd)*>
括號表明要重復的是相互匹配的<dt><dd>元素對。
元素經常以或多或少的隨機順序出現。信息雜志文章通常有一個標題,絕大多數后接文章段落,帶有圖形、照片、副欄、副標題、通篇夾雜的引文,也許在末尾還有作者行??梢栽诟冈芈暶髦性诶ㄌ杻扔秘Q線分組列出所有子元素來指明這些安排。然后您在括號外加星號來指明允許括號內元素出現零或多次。例如:
<!ELEMENT ARTICLE (TITLE, (P | PHOTO | GRAPH | SIDEBAR
| PULLQUOTE | SUBHEAD)*, BYLINE?)>
再舉一例,假設要說明一個DOCUMENT元素,它沒有很多子元素,但必須有一個TITLE后接任意數量的混合文本段落和圖像,以及一個任選的SIGNATURE塊。該元素聲明書寫如下:
<!ELEMENT DOCUMENT (TITLE, (PARAGRAPH | IMAGE)*, SIGNATURE?)>
這不是描述這一結構的唯一方法。實際上這甚至不是最好的方法。另一種方法是聲明一個包含PARAGRAPH和IMAGE元素的BODY元素并把它夾在TITLE和SIGNATURE元素之間,例如:
<!ELEMENT DOCUMENT (TITLE, BODY, SIGNATURE?)>
<!ELEMENT BODY ((PARAGRAPH | IMAGE)*)>
這兩種途徑的區別在于第二種途徑在文檔中使用了BODY元素。這一元素對讀取文檔的應用程序提供了有用的組織層次。問題是文檔的讀者(可能是另一種計算機程序)是否要把BODY作為單一的項目,并同TITLE和SIGNATURE分開,并可從元素總和中區別出來。
再舉一個國際地址的例子。美國以外國家的地址并不遵循美國的約定。尤其是郵政編碼有時在國家名之前,有時則在其后,如下兩例:
Doberman-YPPAN
Box 2021
St. Nicholas QUEBEC
CAN GOS-3LO
或者
Editions Sybex
10/12 Villa Coeur-de-Vey
75685 Paris Cedex 14
France
雖然地址項不是按照順序,郵件也可能郵到目的地,但最好還是讓地址更加方便靈活些。允許靈活性的地址元素聲明可以是這樣:
<!ELEMENT ADDRESS (STREET+, (CITY | STATE | POSTAL_CODE
| COUNTRY)*)>
這表明ADDRESS元素必須有一個或多個STREET子元素后接任意數目的CITY、STATE、POSTAL_CODE或COUNTRY元素。如果要每個元素不多于一個,那這就不夠理想了。遺憾的是,這超出了DTD的能力。您要使元素的順序更加靈活方便,就要放棄一些控制每一元素最大數的能力。
另一方面,可能有一份由任意順序排列的不同元素組成的清單,如一份錄音清單就可能包含CD,唱片集和音帶。區別各類不同元素的元素聲明可能如下:
<!ELEMENT MUSIC_LIST (CD | ALBUM | TAPE)*>
在棒球DTD中,可以使用括號來為投手和擊球手做不同的統計數據集。每名隊員能用一套或另一套數據,但不能用兩者。元素聲明如下:
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION, GAMES,
GAMES_STARTED,((COMPLETE_GAMES?,WINS?,LOSSES?,SAVES?,
SHUT_OUTS?,ERA?,INNINGS?,EARNED_RUNS?,HIT_BATTER?,
WILD_PITCHES?,BALK?,WALKED_BATTER?,STRUCK_OUT_BATTER? )
|(AT_BATS?, RUNS?,HITS?, DOUBLES?, TRIPLES?, HOME_RUNS?,
RBI?,STEALS?,CAUGHT_STEALING?,SACRIFICE_HITS?,
SACRIFICE_FLIES?, ERRORS?, WALKS?, STRUCK_OUT?,
HIT_BY_PITCH ? )))>
在元素聲明中還有一些不好處理的事情。例如,沒有好的方法來說明一份文檔要以TITLE元素開始而以SIGNATURE元素結束,兩者之間可包含其他元素。這是因為ANY不能與其他子元素合用。
還有,通常對元素出現的位置掌握得越不準確,就越不能控制它們的數目。例如,不能說文檔應該有一個可能出現在文檔任何地方的TITLE元素。
但是,用括號來建立元素塊,按順序的元素用逗號分隔,平行出現的用豎線分隔,能讓我們建立帶有詳細的元素出現的位置規則的復雜結構。但是不要無止境地這樣做。簡單的解決方法會更好。DTD越復雜,就越難編寫出滿足要求的合法的文檔,更不要說維護DTD自身的復雜性了。
8.5.11 混合內容
讀者可能已經注意到了,在以前的多數例子中,元素或者包含子元素,或者包含可析的字符數據,但不能同時包含兩者。唯一的例外是以前例子中的一些基本元素。在這些例子中,全部標記的列表還沒有完成。由于基本元素可以包含ANY數據,因而就既可以包含子元素又可以包含原始文本。
可以聲明同時包含子元素和可析字符數據的標記。這就叫做混合內容。這樣就可以給每個TEAM后面加上任意的文本塊。例如:
<!ELEMENT TEAM (#PCDATA | TEAM_CITY | TEAM_NAME | PLAYER)*>
帶有可析的字符數據的混合子元素會嚴重地限制文檔的結構。特別是,只能指定可出現的子元素的名稱。不能限定它們出現的順序,每個元素的出現次數,或者它們是否出現。借助于DTD,利用下面的DTD中的一部分可實現這一要求:
<!ELEMENT PARENT (#PCDATA | CHILD1 | CHILD2 | CHILD3 )* >
除了改變子元素數目以外的其他事情都是不合法的。不能在包括#PCDATA的元素聲明中使用逗號、問號或加號。用豎線分隔的元素和#PCDATA的列表是合法的。其他用法是不合法的。例如,下面的例子就不合法:
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*, #PCDATA)>
使用混合內容的最基本的理由是,當將老式的文本數據轉換成XML的過程中,隨著新標記的增加逐步測試DTD的合法性,而不要在完成全部轉換后再試圖去發現錯誤。這是一個很好的技巧,我建議大家都這樣做,畢竟從剛完成的代碼中立即找出錯誤比幾小時后要容易一些。但是,這僅僅是開發時的一個技巧。最終的用戶是不應該看到這些的。當DTD完成后不能把子元素同可析的字符數據混合起來。一般總可以建立一個包括可析的字符數據的新標記。
例如,可以聲明只包含#PCDATA數據的BLURB元素并把它增加為TEAM的最后一個子元素,這樣就在每個TEAM元素的末尾包括一個文本塊。下面是該聲明:
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*, BLURB)>
<!ELEMENT BLURB (#PCDATA)>
這對文檔的文本改變不大。所有的變化只是向每個TEAM元素增加了一個或多個帶有開始標記和結束標記的可選元素。但是這就使文檔更加健全。而且,從XML程序接收到文檔樹的XML應用程序就能在更短的時間內處理數據,因為文檔具有非混合內容所允許的更為結構化的格式。
8.5.12 空元素
前面討論過,定義一個沒有內容的元素有時是有用的。HTML中的例子包括圖像、水平線和中斷<IMG>、<R>和<BR>。在XML中,這類空元素是通過以/>結束的空標記來標識的,如<IMG/>、<HR/>和<BR/>。
合法的文檔必須聲明使用的空元素和非空元素。因為按定義,空元素沒有子元素,聲明很容易??上裢ǔR粯邮褂冒赵孛?lt;!ELEMENT>來聲明,但用關鍵詞EMPTY (像所有XML標記一樣區分大小寫)代替了子元素的列表。例如:
<!ELEMENT BR EMPTY>
<!ELEMENT IMG EMPTY>
<!ELEMENT HR EMPTY>
清單8-11是同時使用了空元素和非空元素的合法文檔。
清單8-11:使用了空標記的合法文檔
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE DOCUMENT [
<!ELEMENT DOCUMENT (TITLE,SIGNATURE)>
<!ELEMENT TITLE (#PCDATA)>
<!ELEMENT COPYRIGHT (#PCDATA)>
<!ELEMENT EMAIL (#PCDATA)>
<!ELEMENT BR EMPTY>
<!ELEMENT HR EMPTY>
<!ELEMENT LAST_MODIFIED (#PCDATA)>
<!ELEMENT SIGNATURE (HR, COPYRIGHT, BR, EMAIL,
BR, LAST_MODIFIED)>
]>
<DOCUMENT>
<TITLE>Empty Tags</TITLE>
<SIGNATURE>
<HR/>
<COPYRIGHT>1998 Elliotte Rusty Harold</COPYRIGHT><BR/>
<EMAIL>elharo@metalab.unc.edu</EMAIL><BR/>
<LAST_MODIFIED>Thursday,April 22,1999</LAST_MODIFIED>
</SIGNATURE>
</DOCUMENT>
8.6 DTD中的注釋
像一份XML文檔的其他部分一樣,DTD也可以包含注釋。這些注釋不能在聲明中出現,但可以在聲明外出現。注釋通常用來組織不同部分的DTD,為一些元素的許可內容提供說明,并對元素作進一步的解釋。例如,YEAR元素的聲明可以有這樣的注釋:
<!--A four digit year like 1998, 1999, or 2000 ?-->
<!ELEMENT YEAR (#PCDATA)>
像所有注釋一樣,這只是為了便于人們閱讀源代碼,XML處理程序會忽略注釋部分。
注釋的一個可能用法是定義標記中用到的縮略語。例如,在本章及前些章中,我極力避免使用棒球術語的縮略語,因為對一些人來說難以弄清楚。一種可能的途徑是使用縮略語但在DTD中用注釋加以定義。清單8-12同以前的棒球例子相似,但使用了DTD注釋和縮略標記。
清單8-12:使用縮略標記和DTD注釋的合法XML文檔
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>
<!--American or National ?
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!--East , West , or Central ?
<!ELEMENT DIVISION_NAME (#PCDATA)>
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>
<!ELEMENT TEAM_CITY (#PCDATA)>
<!ELEMENT TEAM_NAME (#PCDATA)>
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, P, G,
GS, AB?, R?, H?, D?, T?, HR?, RBI?, SB?, CS?,
SH?, SF?, E?, BB?, S?, HBP?, CG?, SO?, ERA?, IP?,
HRA?, RA?, ER?, HB?, WP?, B?, WB?, K?)
>
<!--=======================-->
<!--Player Info-->
<!--Player's last name-->
<!ELEMENT SURNAME (#PCDATA)>
<!--Player's first name-->
<!ELEMENT GIVEN_NAME (#PCDATA)>
<!-Position-->
<!ELEMENT P (#PCDATA)>
<!--Games Played-->
<!ELEMENT G (#PCDATA)>
<!--Games Started-->
<!ELEMENT GS (#PCDATA)>
<!--=======================-->
<!--Batting Statistics-->
<!--At Bats-->
<!ELEMENT AB (#PCDATA)>
<!--Runs-->
<!ELEMENT R (#PCDATA)>
<!--Hits-->
<!ELEMENT H (#PCDATA)>
<!--Doubles-->
<!ELEMENT D (#PCDATA)>
<!--Triples-->
<!ELEMENT T (#PCDATA)>
<!--Home Runs-->
<!ELEMENT HR (#PCDATA)>
<!--Runs Batted In-->
<!ELEMENT RBI (#PCDATA)>
<!--Stolen Bases-->
<!ELEMENT SB (#PCDATA)>
<!--Caught Stealing-->
<!ELEMENT CS (#PCDATA)>
<!--Sacrifice Hits-->
<!ELEMENT SH (#PCDATA)>
<!--Sacrifice Flies-->
<!ELEMENT SF (#PCDATA)>
<!--Errors-->
<!ELEMENT E (#PCDATA)>
<!--Walks (Base on Balls)-->
<!ELEMENT BB (#PCDATA)>
<!--Struck Out-->
<!ELEMENT S (#PCDATA)>
<!--Hit By Pitch-->
<!ELEMENT HBP (#PCDATA)>
<!--=======================-->
<!--Pitching Staistics-->
<!--Complete Games-->
<!ELEMENT CG (#PCDATA)>
<!--Shut Outs-->
<!ELEMENT SO (#PCDATA)>
<!--ERA-->
<!ELEMENT ERA (#PCDATA)>
<!--Innings Pitched-->
<!ELEMENT IP (#PCDATA)>
<!--Home Runs hit Against-->
<!ELEMENT HRA (#PCDATA)>
<!--Runs hit Against-->
<!ELEMENT RA (#PCDATA)>
<!--Earned Runs-->
<!ELEMENT ER (#PCDATA)>
<!--Hit Batter-->
<!ELEMENT HB (#PCDATA)>
<!--Wild Pitches-->
<!ELEMENT WP (#PCDATA)>
<!-Balk-->
<!ELEMENT B (#PCDATA)>
<!--Walked Batter-->
<!ELEMENT WB (#PCDATA)>
<!--Struck Out Batter-->
<!ELEMENT K (#PCDATA)>
<!--=======================-->
<!--Fielding Statistics-->
<!--Not yet supported-->
]>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East </DIVISION_NAME>
<TEAM>
<TEAM_CITY>Atlanta</TEAM_CITY>
<TEAM_NAME>Braves</TEAM_NAME>
<PLAYER>
<GIVEN_NAME>Ozzie</GIVEN_NAME>
<SURNAME>Guillen</SURNAME>
<P>Short stop</P>
<G>83</G>
<GS>59</GS>
<AB>264</AB>
<R>35</R>
<H>73</H >
<D>15</D>
<T>1</T>
<HR>1</HR>
<RBI>22</RBI>
<SB>1</SB>
<CS>4</CS>
<SH>4</SH>
<SF>2</SF>
<E>6</E>
<BB>24</BB>
<S>25</S>
<HBP>1</HBP>
</PLAYER>
</TEAM>
<TEAM>
<TEAM_CITY>Florida</TEAM_CITY>
<TEAM_NAME>Marlins</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Montreal</TEAM_CITY>
<TEAM_NAME>Expos</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Mets</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Philadelphia</TEAM_CITY>
<TEAM_NAME>Phillies</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>Cubs</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
當整個聯賽全包含在內時,產生的文檔從帶長標記的699K縮短到帶短標記的391K,節約了44%。但是信息的內容是相同的。兩個文檔壓縮后很接近,短標記文檔58K,長標記文檔66K。
在注釋內可以包括和應該包括的信息量沒有限制。包括得多使DTD更長(這樣就使檢測更難,下載更慢)。然而,在下面的幾章中,您將學會如何在多個XML文檔間共享同一DTD以及將DTD拆成更好管理的多個部分。這樣,使用注釋的缺點就是暫時的了。我建議在DTD中自由地使用注釋,尤其是對于打算公用的DTD。
8.7 在文檔間共享通用的DTD
前面的合法的例子都在文檔的序言部分包含了DTD。但是XML真正的功能來自于不同的人們編寫的可為許多文檔共享通用的DTD。如果DTD不是直接包含在文檔內,而是從外部聯結而來,則DTD的改變會自動傳播給使用它的所有文檔。另一方面,當DTD改變時并不能確保其向后兼容性。不兼容的改變會破壞文檔。
當使用外部DTD時,文檔類型聲明要加以改變。DTD不再是包括在方括號中,而是在SYSTEM關鍵詞后接一個能找到DTD的絕對或相對的URL。例如:
<!DOCTYPE root_element_name SYSTEM "DTD_ URL">
這里root_element_name像以前一樣是基本元素的名稱,SYSTEM是一個XML關鍵詞,DTD_URL是能找到DTD的絕對或相對的URL。例如:
<!DOCTYPE SEASON SYSTEM "baseball.dtd">
為說明這一過程讓我們來轉換一個熟悉的例子。清單8-12包括了棒球統計的內部DTD。我們要把這份清單轉換為外部DTD。首先,去掉DTD并把它放入自己的文檔。DTD是起始于<!DOCTYPE SEASON [終止于]>之間的所有內容。但不包括<!DOCTYPE SEASON [和]>??梢詫⑵浔4嬖诿麨?/span>baseball.dtd的文檔內,如清單8-13所示。文檔名并不重要,通常用的擴展名為.dtd。
清單8-13:棒球的DTD文檔
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>
<!--American or National-->
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!--East, West, or Central-->
<!ELEMENT DIVISION_NAME (#PCDATA)>
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>
<!ELEMENT TEAM_CITY (#PCDATA)>
<!ELEMENT TEAM_NAME (#PCDATA)>
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, P, G,
GS, AB?, R?,H?, D?, T?, HR?, RBI?, SB?, CS?,
SH?, SF?, E?, BB?, S?, HBP?, CG?, SO?, ERA?, IP?,
HRA?, RA?, ER?, HB?, WP?, B?, WB?, K?)
>
<!--=======================-->
<!--Player Info-->
<!--Player's last name-->
<!ELEMENT SURNAME (#PCDATA)>
<!--Player's first name-->
<!ELEMENT GIVEN_NAME (#PCDATA)>
<!--Position-->
<!ELEMENT P (#PCDATA)>
<!--Games Played-->
<!ELEMENT G (#PCDATA)>
<!--Games Started-->
<!ELEMENT GS (#PCDATA)>
<!--=======================-->
<!--Batting Statistics-->
<!--At Bats-->
<!ELEMENT AB (#PCDATA)>
<!--uns-->
<!ELEMENT R (#PCDATA)>
<!--Hits--> ?
<!ELEMENT H (#PCDATA)>
<!--Doubles-->
<!ELEMENT D (#PCDATA)>
<!--Triples-->
<!ELEMENT T (#PCDATA)>
<!--Home Runs-->
<!ELEMENT HR (#PCDATA)>
<!--Runs Batted In-->
<!ELEMENT RBI (#PCDATA)>
<!--Stolen Bases-->
<!ELEMENT SB (#PCDATA)>
<!--Caught Stealing-->
<!ELEMENT CS (#PCDATA)>
<!--Sacrifice Hits-->
<!ELEMENT SH (#PCDATA)>
<!--Sacrifice Flies-->
<!ELEMENT SF (#PCDATA)>
<!-Errors-->
<!ELEMENT E (#PCDATA)>
<!--Walks (Base on Balls)-->
<!ELEMENT BB (#PCDATA)>
<!--Struck Out-->
<!ELEMENT S (#PCDATA)>
<!--Hit By Pitch-->
<!ELEMENT HBP (#PCDATA)>
<!--=======================-->
<!--Pitching Staistics-->
<!--Complete Games-->
<!ELEMENT CG (#PCDATA)>
<!--Shut Outs-->
<!ELEMENT SO (#PCDATA)>
<!--ERA-->
<!ELEMENT ERA (#PCDATA)>
<!--Innings Pitched-->
<!ELEMENT IP (#PCDATA)>
<!--Home Runs hit Against-->
<!ELEMENT HRA (#PCDATA)>
<!--Runs hit Against-->
<!ELEMENT RA (#PCDATA)>
<!--Earned Runs-->
<!ELEMENT ER (#PCDATA)>
<!--Hit Batter-->
<!ELEMENT HB (#PCDATA)>
<!--Wild Pitches-->
<!ELEMENT WP (#PCDATA)>
<!-Balk-->
<!ELEMENT B (#PCDATA)>
<!--Walked Batter-->
<!ELEMENT WB (#PCDATA)>
<!--Struck Out Batter-->
<!ELEMENT K (#PCDATA)>
<!--=======================-->
<!--Fielding Statistics-->
<!--Not yet supported-->
接下來,需要改動文檔本身。因為要依賴于另一文檔中的DTD,XML聲明不再是獨立的文檔。所以standalone屬性要改為no,如下所示:
<?xml version="1.0" standalone="no"?>
然后還要改變<!DOCTYPE>標記,借助于包括SYSTEM關鍵字和URL(通常是相對的)使它指向DTD。
<!DOCTYPE SEASON SYSTEM "baseball.dtd" >
文檔的其余部分與以前相同。但是,現在序言部分只包含XML聲明和文檔類型聲明而不包括DTD。清單8-14顯示了這些代碼。
清單8-14:帶有外部DTD的棒球統計
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON SYSTEM "baseball.dtd" >
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Atlanta</TEAM_CITY>
<TEAM_NAME>Braves</TEAM_NAME>
<PLAYER>
<GIVEN_NAME>Ozzie</GIVEN_NAME>
<SURNAME>Guillen</SURNAME>
<P>Shortstop</P>
<G>83</G>
<GS>59</GS>
<AB>264</AB>
<R>35</R>
<H>73</H>
<D>15</D>
<T>1</T>
<HR>1</HR>
<RBI>22</RBI>
<SB>1</SB>
<CS>4</CS>
<S >4</S >
<SF>2</SF>
<E>6</E>
<BB>24</BB>
<S>25</S>
<HBP>1</HBP>
</PLAYER>
</TEAM>
<TEAM>
<TEAM_CITY>Florida</TEAM_CITY>
<TEAM_NAME>Marlins</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Montreal</TEAM_CITY>
<TEAM_NAME>Expos</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Mets</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Philadelphia</TEAM_CITY>
<TEAM_NAME>Phillies</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>Cubs</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
一定要確保清單8-14和baseball.dtd在同一目錄下,然后像通常一樣把清單8-14裝入Web瀏覽器。如果一切正常,就會看到同裝入清單8-12一樣的輸出。現在可以使用這個DTD來編寫其他文檔,如其他年度的統計數據。
如果添加了樣式單,那么就在三個不同的文檔中保存了文檔的三個重要部分。數據在文檔文件中,數據應用的結構和語義在DTD文件中,而格式在樣式單中。這種結構使我們能相對獨立地檢查和改變其中任一部分或全部。
DTD與文檔之間比文檔與樣式單之間聯系更緊密。改變DTD一般要重新檢查文檔的合法性,并需要編輯文檔使它與DTD相符。這樣的順序必要性取決于編輯方法;增加元素沒什么問題,但移走元素就可能有問題。
8.7.1 遠程URL上的DTD
如果一個DTD適用于多份文檔,就不能總把它放在應用它的每份文檔的同一目錄下??梢允褂肬RL來準確指明DTD的地址。例如,讓我們假設棒球DTD在http://metalab.unc.edu/xml/dtds/baseball.dtd,可在序言中使用下面的<!DOCTYPE> 標記將其鏈接到文檔上:
<!DOCTYPE SEASON SYSTEM
"http://metalab.unc.edu/xml/dtds/baseball.dtd">
本例中使用了完整的URL,從任何地方都是合法的。有時也希望從相對于Web服務器文檔根目錄或當前目錄找出DTD來。通常,任何相對于文檔位置所形成合法的URL的引用都可以接受。例如,下面這些都是合法的文檔類型聲明:
<!DOCTYPE SEASON SYSTEM"/xml/dtds/baseball.dtd">
<!DOCTYPE SEASON SYSTEM"/dtds/baseball.dtd">
<!DOCTYPE SEASON SYSTEM "../baseball.dtd">
一個文檔不能有多于一個的文檔類型聲明,即不能有多于一個的<!DOCTYPE >標記。要使用不止在一個DTD中聲明的元素,就需要使用外部參數實體引用。這些內容將在下一章中討論。
8.7.2 公共的DTD
關鍵詞SYSTEM是為單個作者或小組所用的私有的DTD使用的。但作為XML承諾的一部分,可令覆蓋整個產業的廣泛組織(如ISO或IEEE)能夠將公共DTD加以標準化,以便用于各自的專門領域。這樣的標準化可以讓人們不用為同一項目重復作標記,并且使用戶共享公用文檔更容易。
為創建組織之外的編寫者設計的DTD使用PUBLIC關鍵詞而不使用SYSTEM關鍵詞。并且DTD有一個文件名。句法如下:
<!DOCTYPE root_element_name PUBLIC "DTD_name" "DTD_URL">
root_element_name仍然是基本元素名稱。PUBLIC是XML關鍵詞,說明這一DTD是公共使用并具有名稱。DTD_name是與此DTD聯系的名稱。有些XML處理程序會使用名稱從中心庫中檢索DTD。最后,如果DTD不能根據名稱從熟知的庫中檢索到,則DTD_URL是一個能找到該DTD的相對或絕對URL。
DTD名稱與XML名稱略有不同。它們只能包含ASCII字母字符、空格、軟回車符、換行符和下面的標點符號: -'()+,/:=?;!*#@$_%。 而且,公共DTD要遵守一些約定。
如果一項DTD是ISO標準,它的名稱要以字符串"ISO"開始。如果是非ISO標準組織批準的DTD,它的名稱以加號(+)開始。如果不是標準組織批準的DTD,它的名稱以連字符 (-)開始。這些開始字符串后接雙斜線(//) 和DTD所有者的名字,其后接另一個雙斜線和DTD描述的文檔類型,然后又是一個雙斜線后接ISO639語言標識符,如EN表示英語。在http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt處列有完整的ISO639標識符。例如,棒球DTD可以如下命名:
-//Elliotte Rusty Harold//DTD baseball statistics//EN
本例說明這個DTD不是由任何標準組織批準的(-),為Elliotte Rusty Harold所有,描述棒球統計,用英語編寫。通過DTD名稱指向這一DTD的完整的文檔類型聲明如下:
<!DOCTYPE SEASON PUBLIC
"//Elliotte Rusty Harold//DTD baseball statistics//EN"
"http://metalab.unc.edu/xml/dtds/baseball.dtd">
讀者也許注意到了許多HTML編輯器如BBEdit會在其創建的每個HTML文檔開端放入下列字符串:
<!DOCTYPE HTML PUBLIC"-//W3C//DTD HTML//EN">
現在可能 了解這些字符串是什么意思了!它表明文檔符合一項非標準 (-) 的HTML的DTD,由W3C用英語制作。
從技術上說,W3C不是一個標準組織,因為它的成員限于交納會費的公司而不是官方批準的實體。它只出版建議而不是標準。實際上這種區別沒有關系。
8.7.3 內部和外部DTD子集
盡管大多數文檔由易于定義的部分組成,但不是所有的文檔都使用共同的模板。許多文檔為自己使用而增加特定元素時,可能需要像HTML 4.0 DTD 這樣的標準DTD。其他文檔可能只使用標準元素,但需要對它們重新排序。例如,一個HTML主頁可能有一個BODY元素,它必須包含一個H1標題標記后接一份DL定義列表,而另一個HTML主頁可能有一個BODY元素,它包含許多不同的順序不定的標題標記、段落和圖像。如果特定的文檔與同一站點上其他頁面具有不同的結構,在文檔本身內定義結構比在單獨的DTD中定義更有用。這種方法也使文檔更易于編輯。
為達此目的,文檔可使用內部和外部DTD。內部聲明放在<!DOCTYPE>標記尾部的方括號中。例如,假如需要一個包括棒球統計并有頁眉和頁腳的主頁。這樣的文檔可如清單8-15所示。棒球信息從文檔baseball.dtd中得到,構成外部DTD子集。基本元素DOCUMENT 以及元素TITLE和SIGNATURE的定義來自包含于文檔中的內部DTD子集。這有點不尋常。一般的,更為通用的部分可能應該是外部DTD的一部分,而內部內容則更與特定專題有關。
清單8-15:DTD具有內部和外部DTD子集的棒球文檔
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON SYSTEM "baseball.dtd"> [
<!ELEMENT DOCUMENT (TITLE, SEASON, SIGNATURE)>
<!ELEMENT TITLE (#PCDATA)>
<!ELEMENT COPYRIG T (#PCDATA)>
<!ELEMENT EMAIL (#PCDATA)>
<!ELEMENT LAST_MODIFIED (#PCDATA)>
<!ELEMENT SIGNATURE (COPYRIGHT, EMAIL, LAST_MODIFIED)>
]>
<DOCUMENT>
<TITLE>1998 Major League Baseball Statistics</TITLE>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Atlanta</TEAM_CITY>
<TEAM_NAME>Braves</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Florida</TEAM_CITY>
<TEAM_NAME>Marlins</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Montreal</TEAM_CITY>
<TEAM_NAME>Expos</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Mets</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Philadelphia</TEAM_CITY>
<TEAM_NAME>Phillies</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>Cubs</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East </DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
<SIGNATURE>
<COPYRIGHT>Copyright 1999 Elliotte Rusty Harold</COPYRIGHT>
<EMAIL>elharo@metalab.unc.edu</EMAIL>
<LAST_MODIFIED>March 10, 1999</LAST_MODIFIED>
</SIGNATURE>
</DOCUMENT>
在內部和外部DTD子集中的同名元素之間發生沖突的情況下,內部聲明的元素具有優先權。這種優先權提供了不完善部分的繼承機制。例如,如要推翻PLAYER元素的定義,以便只包含擊球統計數據,而不要投球統計數據。這時可使用大多數的棒球DTD的相同聲明,但卻要將PLAYER元素作如下改變:
<!DOCTYPE SEASON SYSTEM "baseball.dtd" [
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, P, G,
GS, AB?, R?, H?, D?, T?, HR?, RBI?, SB?, CS?,
SH ?, SF?, E?, BB?, S?, HBP?)
>
]>
8.8 本章小結
在本章中,學習了如何使用DTD來描述文檔結構,包括文檔包含的必需元素和任選元素,以及這些元素間的相關關系。特別是學習了以下內容:
* 文檔類型定義(DTD),它提供了文檔包含的元素、標記、屬性和實體及相互關系的清單。
* 文檔序言包含文檔類型聲明,文檔類型聲明指明基本元素并包含DTD。DTD處在XML聲明與實際文檔開始之間。由<!DOC-TYPE ROOT [和]>加以界定,ROOT是基本元素名稱。
* DTD列出了文檔的可允許的標記和結構。遵守DTD規則的文檔才是合法的。
* 元素類型聲明聲明元素名稱和子元素。
* 元素類型聲明中用逗號分隔的子元素在文檔中出現的順序必須與聲明中的順序相同。
* 加號表示元素可以出現一次或多次。
* 星號表示元素可以出現零次或多次。
* 問號表示元素可以出現零次或一次。
* 豎線表示可以使用這一個也可以使用另一個元素。
* 括號可以組合子元素,以便使元素聲明更詳盡。
* 混合內容包含元素和可析的字符數據,但會限制父元素可實現的結構。
* 空元素用EMPTY關鍵詞聲明。
* 注釋使DTD 更具可讀性。
* 在文檔類型聲明中利用SYSTEM關鍵詞和一個URL可以定位外部DTD。
* 在文檔類型聲明中用PUBLIC關鍵詞可以定位標準DTD。
* 內部DTD子集中的聲明可推翻外部DTD子集中的聲明。
在下一章中,讀者可學到有關DTD的更多知識,包括實體引用如何提供文本替換,如何將DTD與它所描述的文檔分開,以便易于在文檔間共享。還會學到如何用多份DTD描述單個文檔。
轉載于:https://www.cnblogs.com/sxjrcool/archive/2007/12/22/1010920.html
總結
以上是生活随笔為你收集整理的文档类型定义和合法性(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于AOP
- 下一篇: 企业内部网的IP解析问题