那些閱讀過我前兩篇有關 RELAX NG 的文章( 第 1 部分和 第 2 部分)的讀者會注意到,在我列舉的許多示例中,我選擇使用緊湊語法而不是 XML 語法。這兩種格式在語義上是等同的,但在我看來,緊湊語法更易於閱讀和編寫。而且,這個專欄的讀者通常會覺得我是多麼不贊成這樣一種概念:所有能與 XML 技術扯上關系的事物其本身也必須使用 XML 格式。XSLT 是 XML 無處不在這種趨勢以及這種趨勢的缺陷的主要例證 — 但這是另一專欄所要討論的話題。
在本文的後面,我將比前兩篇更詳細地討論 RELAX NG 緊湊語法的格式。
工具支持
RELAX NG 緊湊語法的不足之處在於,由於它是新鮮事物 — 它在各方面還沒有 100% 定型 — 所以對該語法的工具支持沒有對 XML 語法的工具支持那麼全面。例如,盡管 Java 工具 trang 支持緊湊語法和 XML 語法之間的轉換,但與其關聯的工具 jing 只針對 XML 語法模式進行驗證。顯然,生成 XML 語法 RELAX NG 模式來用於驗證並不十分困難,但直接使用緊湊語法模式將會更方便。類似地,Python 工具 xvif和 4xml 也只針對 XML 語法模式進行驗證。
為了幫助彌補對緊湊語法的直接支持的空缺,我編寫了一個 Python 工具,它可以解析 RELAX NG 緊湊模式,並將它們輸出成 XML 格式。雖然我的 rnc2rng 工具僅僅實現了 trang 所完成的工作,但 Eric van der Vlist 和 Uche Ogbuji 都表示有興趣將 rnc2rng分別包含到 xvif和 4XML 中。理想情況是,在不久的將來,針對緊湊語法模式的直接驗證將包含進這兩個工具。
編寫 rnc2rng 實際上比我預料的要困難;這可能也是一個教訓。雖然 RELAX NG 緊湊語法十分易於閱讀 — 正如您將在下面所看到的 — 但實例之間標記的安排存在眾多變化,這使得編寫解析器不是那麼簡單。不論好壞,我使用 PLY 的 lex 模塊來標記模式,但沒有使用 yacc 進行解析,而選擇了特定於應用程序的標記流的消息傳遞。調試聲明性語法通常比用增量方式來調整命令式代碼更困難。盡管我經常關注 XML 的不友好性,但解析 XML 語法模式這一任務本可以相當簡單,因為我可以讓 SAX 或 DOM 之類的框架來為我做大部分的解析工作。
有關 RELAX NG 編輯器的更多內容
自上篇文章以來,對 RELAX NG 的工具支持得到了一點改進。已經發布了 <oXygen/> V2.0 XML 編輯器,它以插件形式合並了 trang,所以該編輯器提供了對 RELAX NG 一些支持。雖然這裡沒有篇幅作全面回顧,但我發現 <oXygen/> 2.0 — 我從 V1.2 開始就喜歡上它了 — 已經具備了一些良好特性以及吸引人的地方。我願意看到將 RELAX NG 進一步集成到各種編輯器中 — 其集成程度能達到類似於 DTD 和 W3C XML Schema 這樣的程度。我想,再過一段時間,可能會有許多工具將更好地集成 RELAX NG。
語法特性:名稱空間
緊湊語法 RELAX NG 模式可能以幾個可選的名稱空間聲明中的任意一個開始。這些聲明中的每一個都類似於編程語言中的賦值語句。可以用如下方式指定模式標記的缺省名稱空間:
default namespace = "http://relaxng.org/ns/structure/version"
當轉換成 XML 語法時,使用這個聲明會向模式的根元素追加一個 "ns" 屬性。如果沒有顯式地指定這個名稱空間,則使用 缺省的名稱空間,並將其作為根屬性聲明,譬如:
<root-tag XMLns="http://relaxng.org/ns/structure/1.0">
您也可以為元素或屬性聲明外部名稱空間:
namespace foo = "http://some.path.to/foo"
這使您能夠象這樣描述元素:
element foo:bar { ... }
當轉換成 XML 語法時,名稱空間 URL 被添加到根標記,作為根標記的另一個屬性:
<root-tag XMLns="http://relaxng.org/ns/structure/1.0"
XMLns:foo="http://some.path.to/foo">
名稱空間 "a" 在這裡有點特殊。RELAX NG 允許注解(annotation),注解基本上只是帶有 "a" 名稱空間的標記。在緊湊語法中,您可以通過添加一個帶有以兩個井號標記開頭的注解來避免考慮名稱空間:
## An annotation
轉換成 XML 語法時,這個注解顯示為:
<a:documentation>An annotation</a:documentation>
順便說一句,以單個井號開頭的內容為注釋(comment)而非注解,所以下面這個緊湊語法形式:
# This is a comment
所對應的 XML 形式為:
<!-- This is a comment -->
您也可以使用略顯奇怪的緊湊語法形式在 "a" 名稱空間內指定其它注解:
[ a:defaultValue = "foo" ]
如果使用了注解,那麼將在 XML 語法中自動指定根屬性 "XMLns:a" ,但由於 "a" 正是另一個名稱空間,所以如果需要,您可以指定自己的 URL。這個缺省的屬性等同於指定:
namespace a = "http://relaxng.org/ns/compatibility/annotation/1.0"
用這兩種語法形式以不同方式指定另一個特殊名稱空間。數據類型取決於模塊化的規范,通常使用 W3C XML Schema 數據類型。您可以用緊湊語法指定數據類型:
datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes"
或者用 XML 語法指定:
<root-tag XMLns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
語法特性:嵌套和與上下文無關的樣式
RELAX NG 語法的主體可以采用兩種樣式中的一種。在某些方面,較直接的樣式是:僅將元素和屬性嵌於它們應在有效實例中出現的位置。通常,使用在編程語言中所采用的縮進方式是一種好的形式,但正如在 C 家族語言中那樣,花括號是實際塊定界符。一般完整的模式類似於這樣:
清單 1. 嵌套的緊湊語法模式# A library patron example
default namespace = "http://some.other.url/ns"
namespace foo = "http://home.of.foo/ns"
datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes"
## Annotation here
element patron {
element name { xsd:string { pattern = "\\w{,10}" } }
& element id-num { xsd:string }
& element book {
( attribute isbn { text }
| attribute title { text }
| attribute anonymous { empty })
}*
}
圖書館顧客示例用到了大多數語法元素。分布在元素(或屬性)之間的 "&" 表示這幾個元素必須出現,但出現的順序可以是任意的。在 XML 語法中,這等同於 <interleave> 標記。類似地,分布於幾項之間的 "|" 表示可以在其中進行選擇 — 在 XML 中,與該標記相對應的是 <choice> 。還要注意 "book" 元素:圓括號表示這是一個組,但在這裡它們是多余的。然而,作為量化或散置的一部分,組(XML: <group> )很有用。例如:
清單 2. 使用組來量化element foo {
( element bar { text },
element baz { text } )+,
element bam { text } }
在這種情形下,有效文檔的根元素 <foo> 可能在最後一個元素 <bam> 之前包含幾個 <bar></bar><baz></baz> 序列。只通過確定 "bar" 和 "baz" 元素的數量是無法表達這一概念的。
嵌套樣式的 RELAX NG 語法不需要僅僅描述單個元素。任何格式良好的 XML 文檔必須有一個根元素,所以很顯然,在最頂層禁止使用屬性。同樣地,在最頂層的序列或交錯描述不能描述格式良好的 XML 文檔,所以它不能描述有效的文檔。但允許根元素的 選項是不會有錯的,譬如:
( element foo {text}
| element bar {text} )
第二種樣式的 RELAX NG 語法更象 DTD。一開始先指出名為 "start" 的特殊 產品(production),然後緊跟各種其它指定的產品。正如使用名稱空間聲明一樣,用編程語言的賦值方式來命名產品。例如,圖書館顧客模式也可以類似於這樣:
清單 3. 與上下文無關的緊湊語法模式# A library patron example
default namespace = "http://some.other.url/ns"
namespace foo = "http://home.of.foo/ns"
datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes"
## Annotation here
start = patron
patron = name & id-num & book
name = element name { xsd:string { pattern = "\\w{,10}" } }
id-num = element id-num { xsd:string }
book = element book {
( attribute isbn { text }
| attribute title { text }
| attribute anonymous { empty }) }*
在產品中可以出現其它產品的名稱,這可以防止重復,並且通常使復雜的模式更具可讀性。除了可讀性,命名模式還允許遞歸的模式定義 — 或者采用直接遞歸,采用相互遞歸。例如,不可能用嚴格的嵌套樣式來描述 HTML — 在 Html 中,可以在表中嵌套表,或者在列表中嵌套列表。遞歸的 XML 實例文檔的結論是使作為描述的 DTD 和與上下文無關的 RELAX NG 比 W3C XML Schema 更自然(但您 可以通過 W3C XML Schema 獲得您所需的;只是需要做更多工作)。
這裡可能值得研究一個完整的 XML 語法 RELAX NG 模式文檔。為了比較,清單 4 是 rnc2rng 處理清單 3 中與上下文無關的圖書館顧客模式時所生成的內容:
清單 4. 與上下文無關的 XML 語法模式<?XML version="1.0" encoding="UTF-8"?>
<!-- A library patron example -->
<grammar XMLns="http://relaxng/ns/structure/1.0"
ns="http://some.other.url/ns"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
XMLns:a="http://relaxng.org/ns/compatibility/annotations/1.0"
XMLns:foo="http://home.of.foo/ns">
<a:documentation>Annotation here</a:documentation>
<start><ref name="patron"/></start>
<define name="patron">
<interleave>
<ref name="name"/>
<ref name="id-num"/>
<ref name="book"/>
</interleave>
</define>
<define name="name">
<element name="name">
<data type="string"/>
<param name="pattern">\\w{,10}</param>
</data>
</element>
</define>
<define name="id-num">
<element name="id-num">
<data type="string"/>
</element>
</define>
<define name="book">
<zeroOrMore>
<element name="book">
<choice>
<attribute name="isbn"/>
<attribute name="title"/>
<attribute name="anonymous">
<empty/>
</attribute>
</choice>
</element>
</zeroOrMore>
</define>
</grammar>
我想說,這要比 W3C XML Schema 更具可讀性,但它比緊湊語法還差很多(在前面的文章中已經指出實際上 不可能用 W3C XML Schema 或 DTD 精確地表達該模式)。
其它
在其中一些示例中,您會注意到用緊湊語法表示的元素和屬性總是在其名稱後面的花括號中包含 一些內容。在 XML 語法中,您可以自己關閉一個屬性標記,但為了防止不確定性,對於屬性主體,您至少需要指定 {text} 或 {empty} 。當然,如果您願意,也可以使用更復雜的數據類型描述。同樣,對於屬性,唯一有意義的量化是 "?" — 屬性可以是可選的,但它們不能多次重復。
在一些不常見的情形中, rnc2rng不同於 trang。例如,即使在緊湊語法中注解行出現在根元素之前,這兩個工具也都強制注解(在 XML 語法中)出現在根元素內。由於格式良好的 XML 文檔只有一個根元素,所以這是必需的。但 trang也用類似的方式移動注釋,而 rnc2rng 卻不這樣。還有一個小差異,這兩個工具使用空格的方式略微不同。當然還可能存在其它一些不同之處,但從理論上講,這些變化在語義方面都不重要。