VB.NETでもXMLを扱いたい(その5)
はじめに
今まですごくダラダラやってましたが、今回が最後だと思います。
今回はDOMとXPathを用います。LINQ to XMLはお休みです。
サンプルXML
今回のためにサンプル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="alice" address ="alice@jyuch.com"/> <recipient> <to name="bob" address="bob@jyuch.com"/> </recipient> <subject> <text>Hello bob</text> </subject> <body> <text>Who are you?</text> </body> </mail> <mail> <from name="bob" address="bob@jyuch.com"/> <recipient> <to name="alice" address="alice@jyuch.com"/> <cc name="charile" address="charlie@jyuch.com"/> </recipient> <subject> <file path="Re_Hello_bob.txt"/> </subject> <body> <text>I fine thank you. And you.</text> </body> </mail> <mail> <from name="alice" address="alice@jyuch.com"/> <recipient> <to name="bob" address="bob@jyuch.com"/> <bcc name="dave" address="dave@jyuch.com"/> </recipient> <subject> <text>Re:Re:Hello bob</text> </subject> <body> <file path="I_Fine_Thank_you.txt"/> </body> </mail> </mailbox>
Module Module1 Sub Main() Section3() End Sub Function ReadSchema() As XmlSchema Dim xs As XmlSchema Using r = New StreamReader("mailbox.xsd") xs = XmlSchema.Read(r, Sub(sender, e) Console.WriteLine("{0}:{1}", e.Severity, e.Message)) End Using Return xs End Function Sub Section3() Dim xss = New XmlSchemaSet() xss.Add(ReadSchema) Dim xdoc = New XmlDocument() Using r = New StreamReader("mailbox.xml") xdoc.Load(r) End Using xdoc.Schemas = xss xdoc.Validate(Sub(sender, e) Console.WriteLine("{0}:{1}", e.Severity, e.Message)) Dim table = New XmlNamespaceManager(xdoc.NameTable) ' 名前空間の別名を定義 table.AddNamespace("jm", "http://jyuch.com/mailbox") For Each e As XmlNode In xdoc.SelectNodes("jm:mailbox/jm:mail", table) Dim from = e.SelectSingleNode("jm:from", table) Console.WriteLine("mail/from") DisplayAddressAttribute(from) Console.WriteLine("mail/recipient/to") For Each re As XmlNode In e.SelectNodes("jm:recipient/jm:to", table) DisplayAddressAttribute(re) Next Console.WriteLine("mail/recipient/to") For Each re As XmlNode In e.SelectNodes("jm:recipient/jm:to", table) DisplayAddressAttribute(re) Next Console.WriteLine("mail/recipient/cc") For Each re As XmlNode In e.SelectNodes("jm:recipient/jm:cc", table) DisplayAddressAttribute(re) Next Console.WriteLine("mail/recipient/bcc") For Each re As XmlNode In e.SelectNodes("jm:recipient/jm:bcc", table) DisplayAddressAttribute(re) Next Console.WriteLine("mail/subject") Dim subject = e.SelectSingleNode("jm:subject", table) If subject.FirstChild.Name = "text" Then Console.WriteLine("text:{0}", subject.FirstChild.FirstChild.Value) Else Console.WriteLine("path:{0}", subject.FirstChild.Attributes.ItemOf("path").Value) End If Console.WriteLine("mail/body") Dim body = e.SelectSingleNode("jm:body", table) If body.FirstChild.Name = "text" Then Console.WriteLine("text:{0}", body.FirstChild.FirstChild.Value) Else Console.WriteLine("path:{0}", body.FirstChild.Attributes.ItemOf("path").Value) End If Console.WriteLine("----------------------------------") Console.WriteLine() Next End Sub Sub DisplayAddressAttribute(node As XmlNode) Console.WriteLine("name:<{0}> address:<{1}>", node.Attributes.ItemOf("name").Value, node.Attributes.ItemOf("address").Value) End Sub End Module
mail/from name:<alice> address:<alice@jyuch.com> mail/recipient/to name:<bob> address:<bob@jyuch.com> mail/recipient/to name:<bob> address:<bob@jyuch.com> mail/recipient/cc mail/recipient/bcc mail/subject text:Hello bob mail/body text:Who are you? ---------------------------------- mail/from name:<bob> address:<bob@jyuch.com> mail/recipient/to name:<alice> address:<alice@jyuch.com> mail/recipient/to name:<alice> address:<alice@jyuch.com> mail/recipient/cc name:<charile> address:<charlie@jyuch.com> mail/recipient/bcc mail/subject path:Re_Hello_bob.txt mail/body text:I fine thank you. And you. ---------------------------------- mail/from name:<alice> address:<alice@jyuch.com> mail/recipient/to name:<bob> address:<bob@jyuch.com> mail/recipient/to name:<bob> address:<bob@jyuch.com> mail/recipient/cc mail/recipient/bcc name:<dave> address:<dave@jyuch.com> mail/subject text:Re:Re:Hello bob mail/body path:I_Fine_Thank_you.txt ----------------------------------
個人的にはDOMオンリーイベントとかLINQ to XMLよりもDOMとXPathのと折衷が一番扱いやすいと思っています。 XMLを生成するにはLINQ to XMLは楽なんですけどねぇ。
順を追って見てみましょう。 XPathでは名前空間を省略できません。 また、たとえXMLの先頭で名前空間の別名がつけられていてもその前空間の別名は使えません。 当たり前ですね。 別名なんてXMLを書く人が自由に付けられるのですから、それに依存したコードは書けるべきではありません。
そこで、XmlNamespaceManager
を用いてXPath中に出てくる名前空間の別名を管理してやる必要があります。
ここではhttp://jyuch.com/mailbox
という名前空間にjm
という別名をつけています。
あとは<mail>
タグごとに分割して、そのノードコンテキスト内の各要素をXPathから取り出しています。
このノードコンテキスト内というのが結構重要で、ノード階層から横断的に要素を取り出すコードだって書けます。
しかし、ここでは意味のある塊として<mail>
ごとのデータを取り出したかったのでこのようにしています。
XPathで目的のノードのXmlNode
を手に入れたらあとは属性をぶっこぬくなり値を取り出すなりご自由にどうぞといった感じです。
ちなみに、横断的なコードはこんな感じです。
ここではXML文章中の<from>
、<to>
、<cc>
、<bcc>
を全て取り出しています。
Sub Section4() Dim xss = New XmlSchemaSet() xss.Add(ReadSchema) Dim xdoc = New XmlDocument() Using r = New StreamReader("mailbox.xml") xdoc.Load(r) End Using xdoc.Schemas = xss xdoc.Validate(Sub(sender, e) Console.WriteLine("{0}:{1}", e.Severity, e.Message)) Dim table = New XmlNamespaceManager(xdoc.NameTable) table.AddNamespace("jm", "http://jyuch.com/mailbox") Dim q = New StringBuilder() q.Append("jm:mailbox/jm:mail/jm:from|"). Append("jm:mailbox/jm:mail/jm:recipient/jm:to|"). Append("jm:mailbox/jm:mail/jm:recipient/jm:cc|"). Append("jm:mailbox/jm:mail/jm:recipient/jm:bcc") Dim address = xdoc.SelectNodes(q.ToString(), table) For Each it As XmlNode In address Console.WriteLine(it.Name) DisplayAddressAttribute(it) Next End Sub
from name:<alice> address:<alice@jyuch.com> to name:<bob> address:<bob@jyuch.com> from name:<bob> address:<bob@jyuch.com> to name:<alice> address:<alice@jyuch.com> cc name:<charile> address:<charlie@jyuch.com> from name:<alice> address:<alice@jyuch.com> to name:<bob> address:<bob@jyuch.com> bcc name:<dave> address:<dave@jyuch.com>
おわり