本家「@IT」にはない内容をエンジニアライフで技術紹介するコラム。広く議論する場になることを目指します。

第046回_doctypedecl

»
今回はdoctypedeclについて実装します。

doctypedeclのW3Cの勧告内容

----W3C勧告----
2.8 Prolog and Document Type Declaration
・・・・
[Definition: The XML document type declaration contains or points to markup declarations that provide a grammar for a class of documents. This grammar is known as a document type definition, or DTD. The document type declaration can point to an external subset (a special kind of external entity) containing markup declarations, or can contain the markup declarations in an internal subset, or can do both. The DTD for a document consists of both subsets taken together.]
・・・・
Document Type Definition
[28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' [VC: Root Element Type] [WFC: External Subset]
・・・・
Note that it is possible to construct a well-formed document containing a doctypedecl that neither points to an external subset nor contains an internal subset.
・・・・
Validity constraint: Root Element Type
The Name in the document type declaration must match the element type of the root element.
・・・・
Well-formedness constraint: External Subset
The external subset, if any, must match the production for extSubset.
・・・・
If both the external and internal subsets are used, the internal subset must be considered to occur before the external subset. This has the effect that entity and attribute-list declarations in the internal subset take precedence over those in the external subset.
----W3C勧告----
----日本語訳----
2.8 プロローグと文書型宣言
・・・・
[定義: XML-文書型宣言は、文書クラスの文法を提供するマークアップ宣言を含む、または指す。この文法は文書型定義またはDTDとして知られている。文書型宣言は、マークアップ宣言を含む外部サブセット(特別な種類の外部実体)を指すことができる。また、文書型宣言は、内部サブセットで直接マークアップ宣言を含むことができる。もしくはその両方を同時に持つこともできる。文書のDTDは、両方のサブセットを合わせたものから構成する。]
・・・・
文書型定義
[28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' [妥当性制約:ルート要素型] [WFC: 整形式性制約:外部サブセット]
・・・・
外部サブセットを指さず、内部サブセットも含まないDOCTYPE宣言を持った整形式の文書を作る事も可能である事に注意せよ。
・・・・
妥当性制約:ルート要素型
文書型宣言のNameは、ルート要素の要素型にマッチしなければならない。
・・・・
整形式性制約:外部サブセット
もしあるなら、外部サブセットは、生成規則extSubsetにマッチしなければならない。
・・・・
もし外部サブセットと内部サブセットが両方使われた場合、内部サブセットは外部サブセットの前に現れたとみなす。これは、内部サブセットの中の実体宣言と属性リスト宣言が外部サブセットに現れたものよりも優先することを意味する。
----日本語訳----

doctypedeclの仕様整理

呼び方
doctypedeclは、document type declaration を指します。日本語では文書型宣言、DOCTYPE宣言などと呼びます。また、doctypedeclが宣言の中で定義するような文法のことをXML(これはSGMLとしての概念かもしれません)では、
DTDと呼びます。文書型定義と文書型宣言は混同しやすいので、このコラムではdoctypedeclをDOCTYPE宣言と呼びます。

生成規則
生成規則は、
[28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>'
です。

DOCTYPE宣言の役割
DOCTYPE宣言は、
 ルート要素名を指定する
 外部サブセットのURI(ExternalID)を指定する
 内部サブセット(intSubset)を定義する
の3つの役割を担っています。

DOCTYPE宣言におけるパターン
内部サブセットと外部サブセットは省略可能です。また内部サブセットも外部サブセットの両方を省略したDOCTYPE宣言をすることもできます。
DOCTYPE宣言の組み合わせは次のようになります。

内部サブセット 外部サブセット consumeするトークン
――――――――――――――――――――――――――――――――――――――
'<!DOCTYPE' S Name S? '>'
'<!DOCTYPE' S Name S ExternalID S? '>'
'<!DOCTYPE' S Name S? [' intSubset ']' S? '>'
'<!DOCTYPE' S Name S ExternalID S? [' intSubset ']' S? '>'

妥当性制約:ルート要素型
DOCTYPE宣言のNameはルート要素のタグ名と一致するか確認します。

整形式性制約:外部サブセット
これは外部サブセット側の制約なのでここでは解説しません。

DTDの定義とXMLにおける定義の仕方
DTDの概念はXML特有の概念ではありません。Wikipediaによれば「Document Type Definition(文書型定義、DTD)とは、マークアップ言語 SGMLおよびXMLにおいて、文書構造(文書型)を定義するためのスキーマ言語の一つである。」

つまり、上位マークアップ言語のSGMLから引き継いだ概念です。そのためなのか、DTDの用語はW3Cの勧告の中でしっかりとした定義がないまま出てきます。XMLにおけるDTDの定義はDOCTYPE宣言の説明にあるので検討します。

W3Cの勧告は[定義: XML-文書型宣言は、文書クラスの文法を提供するマークアップ宣言を含む、または指す。この文法は文書型定義またはDTDとして知られている。]となっています。

文書型宣言の説明を省くと、
 「文書クラスの文法を文書型定義またはDTD」と呼ぶ
 「文書クラスの文法は文書型宣言のマークアップ宣言が提供する」
よって、
 「DTDはマークアップ宣言で構成する」
と判ります。

「一切のDTDを持たない文書」定義における問題
XMLのためのW3Cの勧告において、DTDの定義が不明確であってもそれが要件に影響がなければ、要件がわかり難いだけで結果には影響しません。

残念ながら、W3Cの勧告の「DTD」の用語が要件に影響します。その一つが実体参照の整形式性制約: 宣言した実体 の条件として出てくる「一切のDTDを持たない文書」という文言です。

ここまでの勧告の内容をDTDイコールDOCTYPE宣言として考えると次のように思えます。

内部サブセット外部サブセットDTD
―――――――――――――――――――――――――――――――――
無(一切無い)
外部サブセットの内容
内部サブセットの内容
外部サブセットと内部サブセットを合わせたもの

この表で問題・・・つまるところW3Cの勧告で説明不十分なのは、次の3つのことです。
1.DOCTYPE宣言は省略できる
# prolog ::= XMLDecl Misc* (doctypedecl Misc*)?
2.内部サブセットを有にしても内部サブセットの中身のマークアップ宣言を空にできる
# intSubset ::= (markupdecl | DeclSep)*
3.外部サブセットを有にしても外部サブセットの中身のマークアップ宣言を空にできる
# extSubsetDecl ::= ( markupdecl | conditionalSect | DeclSep)*

1の場合はDTDが無いと同義でしょうか?
2は内部サブセットが無い場合と同義でしょうか?
3は外部サブセットが無い場合と同義でしょうか?

残念ながら不明確です。これに対する1つの推測を52回のコラムで行う予定です。

有(空でない)・有(空でない)の場合の処理の順序とマージの仕方
 内部サブセットと外部サブセットを同時に定義・指定した場合、
かつ、
 外部サブセットと内部サブセットで同じ宣言がある場合、
内部サブセットの内容を優先します。

処理で言えば、外部サブセットを読み込んだ後、内部サブセットで上書きしてしまえば簡単なように思います。生成規則もExternalIDの後にintSubsetが出てくるので自然に処理できるように思います。ところが、「出現順序(処理の順序)は内部サブセットを先に出現したものと見なす」と記載があり、マージが面倒だなと思うかもしれません。

しかし、これには意味があります。今後のコラムで解説しますが、内部サブセットと外部サブセットのそれぞれの中のマークアップ宣言に重複があった場合に先に宣言したものを優先(採用)します。それと同様の処理になるように、内部サブセットと外部サブセットの間に重複があった場合も先に宣言したものを優先する--つまり、内部サブセットの後に外部サブセットの内容が宣言されたものと見なす--ことを念頭に置いた表現にしてあります。

結果、処理は次のようになります。
 1. 内部サブセットのデータを作成する
 2. 外部サブセットのデータを作成する
 3. 内部サブセットのデータに外部サブセットのデータを追加する
  3-1. 外部サブセットのデータ名で内部サブセットのデータが無いか確認する
  3-2. データ名が未だ無い場合、追加する
  3-3. データ名が既に有る場合、追加しない

DOCTYPE宣言におけるサブセット
DOCTYPE宣言やXML文書におけるサブセットは、
 マークアップ宣言

 マークアップ宣言でないもの
から成ります。内部サブセットと外部サブセットの詳細については、別のコラムで説明します。

doctypedeclの実装

実装に必要な機能
doctypedeclを読み込んだ後に必要なデータは
 Nameを示す文字列 : 妥当性制約を検証するために必要
ということが判ります。

DoctypeData

public class DoctypeData
{
    private final static DoctypeData m_instance = new DoctypeData();
    private String m_name;
    
    private DoctypeData()
    {
        m_name = "";
    }
    
    public static DoctypeData getInstance()
    {
        return m_instance;
    }
    
    public void setName(String name)
    {
        m_name = name;
    }
    
    public String getName()
    {
        return m_name;
    }
}
DoctypedeclVisitor

public class DoctypedeclVisitor extends MakingXmlDeclDataVisitor
{
    private static final DoctypeData m_doctypedata
                          = DoctypeData.getInstance();

    public DoctypedeclVisitor()
    {
    }
    
    @Override
    public void visit(EbnfDoctypedeclNode n)
    {
        super.visit(n);
        
        //memo
        // 内部サブセットの解析が完了した後
        // 外部サブセットを取得してマージ処理する
        //
        if (m_result)
        {
            //ExternalIDがあるならば、それを使って外部サブセットを処理する
        }
        else
        {
            //何もしない
        }
    }

    @Override
    public void visit(EbnfDoctypeNameNode n)
    {
        Token token = m_tokenManager.nextToken();
        m_result = token.match(n);
        
        if(m_result)
        {
            String name = token.getString();
            //処理に成功したので登録する
            m_doctypedata.setName(name);
        }
        else
        {
            //何もしない
        }
    }
}
内部サブセットと外部サブセットの構築処理は次回以降に順を追って行います。
Comment(0)

コメント

コメントを投稿する