読者です 読者をやめる 読者になる 読者になる

VB.NETでもXMLを扱いたい(その1)

はじめに

ちょっとXML(+XSD)を扱う必要がありまして、色々試行錯誤したのでそれについてです。

弊社はAntのビルド設定ファイルを割合いじっていたのでXMLを書く事自体はそこまで初心者って事でもないと信じて明日も力強く生きて行きたいのですが、XSDについては存在や『こんな事に使えるんだなー』程度の認識しかなかったのでどちらかと言うとXSDの書き方的な側面が大きいです。

今回の目標はXMLスキーマを定義してXMLの検証をした後にXMLからデータを取り出してグヘヘ出来るところまでです。

とりあえずXML

題材は複数のメールを含むメールボックスを表すXMLとしましょう。 テキトーなプロジェクトをでっち上げて、以下の内容のXMLファイルをmailbox.xmlという名前で追加します。

<?xml version="1.0" encoding="utf-8" ?>
<mailbox>
  <mail>
    <from name="hoge from" address="hoge.from@jyuch.com"/>
    <recipient>
      <to name="hoge to" address="hoge.to@jyuch.com"/>
      <cc name="hoge cc" address="hoge.cc@jyuch.com"/>
      <bcc name="hoge bcc" address="hoge.bcc@jyuch.com"/>
    </recipient>
    <subject>
      <text>Mail Subject</text>
      <!--
      もしくは
      <file path="mailsubject.txt">
      -->
    </subject>
    <body>
      <text>Mail Body</text>
      <!--
      もしくは
      <file path="mailbody.txt">
      -->
    </body>
  </mail>
  <!-- 同様な <mail>...</mail>が続く -->
</mailbox>

軽く説明としますと、<mail>は単一のメールを表し、<mailbox>に1つ以上含まれます。 <from>はメールの送信者を表し、<recipient>はメールの受信者を表します。 <to>は必ず1つ以上含まれ、<cc><bcc>は0個以上の任意の数を含められます。 <to><cc><bcc>は同一の属性nameaddressを持ちます。 同一の属性を持つタグをなぜ別々に定義したかというと、今後の説明のためにこうすると都合がいいのです。 説明のためにあえて無駄なことをしています。

<subject><body><text>で直接指定でき、かつ<file>で外部のテキストファイルを指示もできます。 <file>はテキストファイルのパスを表すpath属性を持っています。

Markdownで以上の説明を書くとかなりめんどいですね。とりあえず、こいつのXMLスキーマを定義します。 と言ってもある程度はVisual Studioが生成してくれるので、それを利用しようと思います。

XMLスキーマの定義

ソリューションエクスプローラーからプロジェクトを右クリックして追加→新しい項目から『XML To Schema』を選んでmailbox.xsdで追加します。 『XMLドキュメントからXMLスキーマセットの生成』というダイアログが出てくるので、『ファイルから追加』を選択し、先ほど追加したmailbox.xmlを追加し『OK』を押下します。

するとこんなXMLスキーマが生成されたと思います。

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" 
           elementFormDefault="qualified" 
           xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  <xs:element name="mailbox">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="mail">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="from">
                <xs:complexType>
                  <xs:attribute name="name" type="xs:string" use="required" />
                  <xs:attribute name="address" type="xs:string" use="required" />
                </xs:complexType>
              </xs:element>
              <xs:element name="recipient">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="to">
                      <xs:complexType>
                        <xs:attribute name="name" type="xs:string" use="required" />
                        <xs:attribute name="address" type="xs:string" use="required" />
                      </xs:complexType>
                    </xs:element>
                    <xs:element name="cc">
                      <xs:complexType>
                        <xs:attribute name="name" type="xs:string" use="required" />
                        <xs:attribute name="address" type="xs:string" use="required" />
                      </xs:complexType>
                    </xs:element>
                    <xs:element name="bcc">
                      <xs:complexType>
                        <xs:attribute name="name" type="xs:string" use="required" />
                        <xs:attribute name="address" type="xs:string" use="required" />
                      </xs:complexType>
                    </xs:element>
                  </xs:sequence>
                </xs:complexType>
              </xs:element>
              <xs:element name="subject">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="text" type="xs:string" />
                  </xs:sequence>
                </xs:complexType>
              </xs:element>
              <xs:element name="body">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="text" type="xs:string" />
                  </xs:sequence>
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

まぁ確かに間違ってはいないのですが、何かの嫌がらせかよって思うレベルでクッソ分かりにくいのと現状では複数mail要素を表現できないのでそこをどうにかします。

という事でこちらに整形済みのものを用意しました。

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified"
           elementFormDefault="qualified"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="http://jyuch.com/mailbox"
           xmlns:mb="http://jyuch.com/mailbox">

  <xs:element name="mailbox">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="mb:mail" maxOccurs="unbounded"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:element name="mail">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="mb:from"/>
        <xs:element ref="mb:recipient"/>
        <xs:element ref="mb:subject"/>
        <xs:element ref="mb:body"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:attributeGroup name="mailAddress">
    <xs:attribute name="name" type="xs:string" use="required" />
    <xs:attribute name="address" type="xs:string" use="required" />
  </xs:attributeGroup>

  <xs:element name="from">
    <xs:complexType>
      <xs:attributeGroup ref="mb:mailAddress"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="recipient">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="mb:to" maxOccurs="unbounded"/>
        <xs:element ref="mb:cc" minOccurs="0" maxOccurs="unbounded"/>
        <xs:element ref="mb:bcc" minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:element name="to">
    <xs:complexType>
      <xs:attributeGroup ref="mb:mailAddress"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="cc">
    <xs:complexType>
      <xs:attributeGroup ref="mb:mailAddress"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="bcc">
    <xs:complexType>
      <xs:attributeGroup ref="mb:mailAddress"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="subject">
    <xs:complexType>
      <xs:choice>
        <xs:element name="text" type="xs:string" />
        <xs:element ref="mb:file" />
      </xs:choice>
    </xs:complexType>
  </xs:element>

  <xs:element name="body">
    <xs:complexType>
      <xs:choice>
        <xs:element name="text" type="xs:string" />
        <xs:element ref="mb:file" />
      </xs:choice>
    </xs:complexType>
  </xs:element>

  <xs:element name="file">
    <xs:complexType>
      <xs:attribute name="path" type="xs:string" use="required" />
    </xs:complexType>
  </xs:element>

</xs:schema>

これでも十分分かりにくいかもしれませんが、これでも頑張ったのです。

弊社も細かいところがよく分かってないまずは細かいところを気にせずにこんな感じにできるのか〜程度で感覚で掴んでほしい*1のですが、全体的な事を説明します。

先頭から見ていくとtargetNamespace="http://jyuch.com/mailbox"で自分自身の名前空間を定義し、xmlns:mb="http://jyuch.com/mailbox"で自分自身の名前空間の省略形を宣言しています。

各要素では直接子要素を定義するのではなく、外部で定義した子要素を参照という形で取り込んでいます。 そうする事で個々の要素の定義が肥大化せずに全体を掴みやすくなるという利点があります。 まぁ、バラし過ぎても逆に分かりにくくなるのはプログラミングと同じですね。

また、xs:attributeGroupで属性の塊を要素とは別に定義することによって同一の属性を持つ事なる要素に対して属性の定義を使いまわせるようになります。

これがやりたかったので、わざわざ別のタグとして定義しました。

<xs:attributeGroup name="mailAddress">
  <xs:attribute name="name" type="xs:string" use="required" />
  <xs:attribute name="address" type="xs:string" use="required" />
</xs:attributeGroup>

<xs:element name="from">
  <xs:complexType>
    <xs:attributeGroup ref="mb:mailAddress"/>
  </xs:complexType>
</xs:element>

他のタグとか属性とかの細かいところはフィーリングで感じ取ってググってください。多分そっちの方が分かりやすい説明があると思います。

そして、このXMLスキーマを適用した後のmailbox.xmlは以下のような感じになります。

<?xml version="1.0" encoding="utf-8" ?>
<mailbox xmlns="http://jyuch.com/mailbox"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://jyuch.com/mailbox mailbox.xsd">
  <mail>
    <from name="hoge from" address="hoge.from@jyuch.com"/>
    <recipient>
      <to name="hoge to" address="hoge.to@jyuch.com"/>
      <cc name="hoge cc" address="hoge.cc@jyuch.com"/>
      <bcc name="hoge bcc" address="hoge.bcc@jyuch.com"/>
    </recipient>
    <subject>
      <text>Mail Subject</text>
    </subject>
    <body>
      <text>Mail Body</text>
    </body>
  </mail>

  <mail>
    <from name="hoge from2" address="hoge.from2@jyuch.com"/>
    <recipient>
      <to name="hoge to2" address="hoge.to2@jyuch.com"/>
    </recipient>
    <subject>
      <file path="subject.txt"/>
    </subject>
    <body>
      <file path="body.txt"/>
    </body>
  </mail>
</mailbox>

とまぁ、今回はXMLスキーマを定義して終わりです。 まだVBのVの字も出てきていないって? いやだってこれ8500字超えてんだよ。 HatenaさんのMarkdownプロセッサが5秒くらい返ってこないんだよ。

つづく

*1:弊社は細かいところを気にせず感覚でコードを書いているところがあるので