mikeo_410


 DTDの効果

  DTD(Document Type Definition)は、独立したファイルでも、インラインでも記述できます。
  インラインで記述する場合は、左図のように、<!DOCTYPE ... > の中に記述します。

  左図は、Visual SudioでXMLファイルを作成している様子です。
  「哺乳類」をルートとするドキュメントを定義しています。
  この定義(DOCTYPE)に続いて、'<' をキーインすると入力補助の選択肢が現れます。
  選択肢には、「哺乳類」が含まれています。

   DTDの定義はエディタやブラウザで、1)入力支援、2)バリデーション、3)ドキュメント(ノードリスト)の生成に使われています。
   XmlReaderは、DTDを読み込んで、XMLを読み込む用意をします。
   しかし、XmlReaderでDTDをドキュメントとして扱うことはできないようです。<!ELMENT>などの記述は、<!DOCTYPE>内にないとエラーになります。


  DTDの宣言部分は概ね以下のようなものです。

  ルート要素名 [
      !ELEMENT、!ATTLIST の繰り返し
  ]

ELEMENT、ATTLIST

  これは、XMLが、「要素」と「属性」、「コンテンツ」の3つを扱っていて、階層構造を持つのが要素と属性であることに対応しています。
  要素は、要素間に階層構造を持ち、要素はいくつかの属性を持ちます。

  1. <!DOCTYPE 哺乳類 [
  2.   <!ELEMENT 哺乳類 (ネコ|ヒト)*>
  3.   <!ELEMENT ネコ EMPTY>
  4.   <!ELEMENT ヒト EMPTY>
  5.   <!ATTLIST ネコ  (ブルー|グリーン|オッドアイ) "ブルー"
  6.                  しっぽ (長い|短い) "長い">
  7.   <!ATTLIST ヒト  (ブルー|) ""
  8.                  呼称 (ママ|召使|よそ者) #IMPLIED>
  9. ]>

  この定義は、ルートが「哺乳類」要素であり、「哺乳類」は、「ネコ」か「ヒト」要素の繰り返しであると定義しています。
  そして、「ネコ」要素は、「瞳」属性と「しっぽ」属性を持ちます。
  Visual Studio のエディタで試すと、以下のように入力支援が行われます。

  !ELEMENT の宣言は、子となりうる要素を列挙するものです。合わせて、必須であるかどうかと言った条件を与えます。
  また、「<哺乳類>」 と入力すると、終了タグ</哺乳類> が補完されます。「<ネコ」 の場合は、<ネコ/> と補完されます。これは、EMPTYの指定がコンテンツがないことを示しているためです。
  !ATTLISTは、記述できる属性の名前と値の組を列挙します。
  このような要素の性質を与えるいくつかのオプションがあります。

ENTITY

  DTDの宣言には、さらにENTITYがあります。
  ENTITYは、ドキュメントの構造とは関係なく、単に名前と値の組を宣言します。
  名前の参照箇所を値で置き換えます。
  ENTITYの宣言には2つ形式があり、使用箇所目的が異なります。

2種類の参照
BNF定義での呼称 参照箇所 記述型式 宣言の例
宣言 参照
EntityRef XML Name &Name; <!ENTITY yen    "&#165;" >
PEReference DTD %  Name %Name; <!ENTITY % linestyle "none|solid|dashed">

  EntityRef は、DTDを使うXML記述で使うこのが目的で、DTDの宣言では参照されていません。
  具体的には、引用符を &quot; と記述するような実体参照で、XML中の記述を1文字に置き換えるのに使われます。
  
  PEReference はDTDの宣言でのみ、宣言と参照が行われています。PEReferenceは、文字列の置き換えを行います。

  1. 上の表の例のように、複数個所に同じ文字列を記述する代わりに、文字列に名前を付けて、参照するようにします。
  2. <!ENTITY % abs.qname "%MATHML.pfx;abs" > のような部分文字列の置換を行います。
    <!ENTITY % MATHML.prefix "m" >
    <!ENTITY % MATHML.pfx  "%MATHML.prefix;:" >のようにして、MathMLの要素名 m:abs を作っています。
  3. また、条件によって宣言を変えるのに使われています。ただし、切り替え条件はDTD内に固定的に宣言されているので目的はわかりません。
    1. <![%MATHML.prefixed;[
    2. <!ENTITY % MATHML.xmlns.extra.attrib  "" >
    3. ]]>
    MATHML.prefixed の値を、INCLUDE とするか、IGNORE とするかで、ENTITYの宣言が行われたり、行われなかったりします。

XmlReader と DTD

DTDファイルを読み込むと

  XmlReaderでDTDファイルを読み込むと、DOCTYPEが先行していない旨のエラーになります。
  DTDの宣言に使われる !ELEMENT などの記述は、DOCTYPE内でのみ可能で、DTD自体を調べる目的にはXmlReaderは使えないようです。

DTDがないとき

  DTD記述は必須ではなく、DOCTYPEのないXMLは普通にパースできます。

  1.     XmlReader xr = XmlReader.Create(
  2.         File.OpenRead("../../XMLFile1.xml"));
  3.     while (xr.Read())
  4.     {
  5.         Debug.WriteLine(xr.LocalName +" "+ xr.NodeType);
  6.         for (int i = 0; i < xr.AttributeCount; i++)
  7.         {
  8.             xr.MoveToAttribute(i);
  9.             Debug.WriteLine("   " + xr.Name + " " + xr.Value);
  10.         }
  11.     }
  12. }
入力 (XMLFile1.xml) 出力
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <E1 A1="V1">
  3.   <E11 A1="V1" A2="V2"/>
  4. </E1> 
  1. xml XmlDeclaration
  2.    version 1.0
  3.    encoding utf-8
  4.  Whitespace
  5. E1 Element
  6.    A1 V1
  7.  Whitespace
  8. E11 Element
  9.    A1 V1
  10.    A2 V2
  11.  Whitespace
  12. E1 EndElement
  13.  Whitespace

  この場合、要素間の親子関係や、要素と属性の関係は調べられていないことになります。
  < > の不正や、終了していない要素などはエラーが検出されます。

DTDのあるXML

  DTDの記述をしたXMLを読み込もうとすると、エラーになります。エラーの理由は、「セキュリティ上の理由」とありますが理解できません。
  このエラーは、DTDを外部ファイルとしたときも、インラインで記述したときも同様です。
  しかし、「XmlReaderSettings の DtdProcessing プロパティを Parse に設定」と、対策も表示されるので、これにしたがって見ます。

  1. XmlReaderSettings settings = new XmlReaderSettings();
  2. settings.DtdProcessing = DtdProcessing.Parse;
  3. XmlReader xr = XmlReader.Create(
  4.     File.OpenRead("../../XMLFile2.xml"), settings);
  5. while (xr.Read())
  6. {
  7.     Debug.WriteLine(xr.LocalName +" "+ xr.NodeType);
  8.     for (int i = 0; i < xr.AttributeCount; i++)
  9.     {
  10.         xr.MoveToAttribute(i);
  11.         Debug.WriteLine("   " + xr.Name + " " + xr.Value);
  12.     }
  13. }
XML (XMLFile2.xml) 出力
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <!DOCTYPE E1 SYSTEM "../../test2.dtd">
  3. <E1 A1="V1">
  4.   <E11 A1="V1" A2="V2"/>
  5. </E1>
  1. xml XmlDeclaration
  2.    version 1.0
  3.    encoding utf-8
  4.  Whitespace
  5. E1 DocumentType
  6.    SYSTEM ../../test2.dtd
  7.  Whitespace
  8. E1 Element
  9.    A1 V1
  10.  Whitespace
  11. E11 Element
  12.    A1 V1
  13.    A2 V2
  14.  Whitespace
  15. E1 EndElement
  16.  Whitespace
DTD (test2.dtd)
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <!ELEMENT E1 (E9)* >
  3. <!ELEMENT E9 (#PCDATA) >

  この例では、DTD(test2.dtd)とXML記述が合っていません。DTDでは、E1要素は、E9要素を持ちますが、XML(XMLFile2.xml)では、E11が子要素になっています。
  XmlReaderは、デフォルトではバリデーションを行わないもののようです。

バリデーション

  バリデーションを行うには、設定が必要なようです。

  1. XmlReaderSettings settings = new XmlReaderSettings();
  2. settings.DtdProcessing = DtdProcessing.Parse;
  3. settings.ValidationType = ValidationType.DTD;
  4. settings.ValidationEventHandler 
  5.     += new System.Xml.Schema.ValidationEventHandler(
  6.         settings_ValidationEventHandler);
  7. XmlReader xr = XmlReader.Create(
  8.     File.OpenRead("../../XMLFile2.xml"), settings);
  9. while (xr.Read())
  10. {
  11.     Debug.WriteLine(xr.LocalName +" "+ xr.NodeType);
  12.     for (int i = 0; i < xr.AttributeCount; i++)
  13.     {
  14.         xr.MoveToAttribute(i);
  15.         Debug.WriteLine("   " + xr.Name + " " + xr.Value);
  16.     }
  17. }

  ValidationType と ValidationEventHandler を設定すると、不正な箇所で ValidationEventHandler  を呼び出すようになります。

  1. void settings_ValidationEventHandler(
  2.     object sender, System.Xml.Schema.ValidationEventArgs e)
  3. {
  4.     Debug.WriteLine(e.Message);
  5. }

  実行すると、以下の出力になります。

  1. xml XmlDeclaration
  2.    version 1.0
  3.    encoding utf-8
  4.  Whitespace
  5. E1 DocumentType
  6.    SYSTEM ../../test2.dtd
  7.  Whitespace
  8. 'A1' 属性が宣言されていません。
  9. E1 Element
  10.    A1 V1
  11.  Whitespace
  12. 要素 'E1' には無効な子要素 'E11' が含まれています。必要とされる要素は 'E9' です。
  13. 'E11' 要素が宣言されていません。
  14. E11 Element
  15.    A1 V1
  16.    A2 V2
  17.  Whitespace
  18. E1 EndElement
  19.  Whitespace

  この場合、警告が行われ、解析は継続しています。

未定義の実体参照

  DTD記述に一致しなくても、XmlReaderはパースして、ノードリストを生成しています。XMLとして正しければ良いようです。
  しかし、以下のような参照が解決しないケースは、エラーとなって解析は打ち切られます。

XML (XMLFile2.xml) DTD (test1.dtd)
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <!DOCTYPE E1 SYSTEM "../../test1.dtd">
  3. <E1 A1="V1">
  4.   <E11 A1="&xxx;" A2="V2"/>
  5. </E1>
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <!ELEMENT E1 (E11)* >
  3. <!ELEMENT E11 (#PCDATA) >
  4. <!ATTLIST E1 A1 CDATA #IMPLIED>
  5. <!ATTLIST E11 A1 CDATA #IMPLIED
  6.               A2 CDATA #IMPLIED>
  7. <!--<!ENTITY xxx "#123;">-->

  DTDは、内部で使用するドキュメント(ノードリスト)を生成するのに、この点で係っています。
  この参照をそのままにして(E11要素のA1属性の値を &xxx; と取得できるように)、ドキュメントを生成する方法はわかりません。



mikeo_410@hotmail.com