XMLの値

タグの編集
投稿者 mayopee  (社会人) 投稿日時 2018/8/31 11:55:26
XDocument及び、XmlDocumentより値を取得する方法を教えてください。
以下のコードでは、いずれもinnerTextである「山田太郎 40」が返却されます。
希望する値は以下の通りです。

●ID要素は属性のみなので値としてはString.Emptyが欲しい
●Name要素は「山田太郎」のみが欲しい

XMLの設計がまずいということは置いといて、もし以下のような構造のXMLが
あったらという事で、お願いします。

Dim Doc =
       <?xml version="1.0" encoding="utf-8"?>
       <Root>
           <ID id="1">
               <Name>山田太郎
                    <Age>40</Age>
               </Name>
           </ID>
       </Root>

        Console.WriteLine("●XDocumentの場合")
        'ID要素は属性のみなので値としてはString.Emptyが欲しい 
        Dim element1 As XElement = Doc.<Root>.<ID>.First
        Console.WriteLine(String.Format("タグ名:{0}       値:{1}", element1.Name, element1.Value))

        'Name要素は「山田太郎」のみが欲しい 
        Dim element2 As XElement = Doc.<Root>.<ID>.<Name>.First
        Console.WriteLine(String.Format("タグ名:{0}       値:{1}", element2.Name, element2.Value))

        Console.WriteLine()
        Console.WriteLine("●XmlDocumentの場合")
        Dim xmlDoc As New XmlDocument
        xmlDoc.LoadXml(Doc.ToString)
        'ID要素は属性のみなので値としてはString.Emptyが欲しい 
        Dim node1 As XmlNode = xmlDoc.SelectSingleNode("/Root/ID")
        Console.WriteLine(String.Format("タグ名:{0}       値:{1}", node1.Name, node1.InnerText))

        'Name要素は「山田太郎」のみが欲しい 
        Dim node2 As XmlNode = xmlDoc.SelectSingleNode("/Root/ID/Name")
        Console.WriteLine(String.Format("タグ名:{0}       値:{1}", node2.Name, node2.InnerText))
    End Sub 
投稿者 mayopee  (社会人) 投稿日時 2018/8/31 12:29:31
すみません。環境を忘れていました。
VB2010です。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2018/9/1 00:39:34
「どういう結果を得たいのか」は説明されているのですが、
肝心の「どの部分の情報を取得したいのか」の説明が不足しているようで…。


> ●ID要素は属性のみなので値としてはString.Emptyが欲しい

ID 要素は空要素では無いですよね。
ここでいう『値』とは何を指していますか?


とりあえず、ID 要素の直下にある Text ノードのみを得たいという意味で良いでしょうか?

もしそれで構わないなら、直下の XText を繋げてみるのは如何でしょう。たとえば
Dim value = String.Join("", element1.Nodes().OfType(Of XText)().Select(Function(n) n.Value))

とすることができます。
(このままだと冗長的なので、実際に使う時は拡張メソッドにしておいた方が良いかも)

ただし、元の空白を保持させておいた場合には、
改行や空白を含んだ文字列になりえることにご注意ください。



> Dim element1 As XElement = Doc.<Root>.<ID>.First
> Console.WriteLine(String.Format("タグ名:{0}       値:{1}", element1.Name, element1.Value))

ということで、空白の扱いについて。

───────
今回は埋め込みの XDocument リテラルで記述しておられるようなので、
一部の改行やインデント空白が破棄され、element1 は下記の内容になります。

『<ID id="1">
  <Name>山田太郎
                    <Age>40</Age></Name>』



element1.Attributes().Count() は『1』を返します。
element1.Attributes()(0) は Attribute ノード『id="1"』を返します。

element1.Nodes().Count() は 『1』 を返します。
element1.Nodes()(0) は Element ノードです。これは element2 変数が示す XElement にあたります。

element2 には、下記の Element ノードが入ります。
『<Name>山田太郎
                    <Age>40</Age></Name>』


そして上記の element2 は、直下に 2 つの子ノードを持っています。
Text ノード『山田太郎{改行}␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣』
Element ノード『<Age>40<Age>』
が得られるという想定です。


この場合、直下の XTextのみを拾って繋げると、
element1 からは『』すなわち String.Empty
element2 からは『山田太郎{改行}␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣』
が得られるという想定です。


───────
もしも XDocument が LoadOptions.PreserveWhitespace 指定で読み込まれていた場合、
上記の element1 には下記の Element ノードが入ることになります。

『<ID id="1">
               <Name>山田太郎
                    <Age>40</Age>
               </Name>
           </ID>』


element1.Attributes().Count() は『1』を返します。
element1.Attributes()(0) は Attribute ノード『id="1"』を返します。

element1.Nodes().Count() は 『3』 を返します。
element1.Nodes()(0) は Text ノード『{改行}␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣』を指します。
element1.Nodes()(1) は Element ノードです。これは element2 変数が示す XElement にあたります。
element1.Nodes()(2) は Text ノード『{改行}␣␣␣␣␣␣␣␣␣␣␣』を指します。

element2 には、下記の Element ノードが入ります。
『<Name>山田太郎
                    <Age>40</Age>
               </Name>』


そして上記の element2 は、直下に 3 つの子ノードを持っています。
Text ノード『山田太郎{改行}␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣』
Element ノード『<Age>40<Age>』
Text ノード『{改行}␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣』


この場合、直下の XTextのみを繋げると、
element1 からは『{改行}␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣{改行}␣␣␣␣␣␣␣␣␣␣␣』
element2 からは『山田太郎{改行}␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣{改行}␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣』
が得られるという想定です。
投稿者 mayopee  (社会人) 投稿日時 2018/9/1 09:00:30
魔界の仮面弁士様、説明不足にも関わらず意図を汲んで下さり、ありがとうございます。

教えて頂いた方法で希望する結果を得られました。DOMの方もOfType(Of XText)の部分を
OfType(Of XmlText)に変更することで同じ結果が得られました。
早速、拡張メソッドを作って自作ライブラリに登録しました。

原因は自分が親子関係を理解できていない為だと思います。
XElementの下に目視では確認できないXNodeがぶらさがっているのが解りませんでした。
DOMでノードを列挙した時も#textという名前のTextノードが列挙されて意味不明でしたが
これで納得しました。
又、改行や空白についてもファイルからLoadしてLoadOptions.PreserveWhitespaceを
指定して確認しました。解説通りの結果となる事を確認しました。

所で、話題は少々ずれますが、今回の質問でもXDocument(以下:LINQ式)、XmlDocument(以下:DOM式)両方で質問しました。
自分はほとんどLINQ式を使いますが、DOM式にしかできない事、又DOM式がLINQ式
に比べて優位な部分があるのでしょうか?
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2018/9/3 00:35:24
> 解説通りの結果となる事を確認しました。

後半の解説が一部間違っていて申し訳ありません。
いずれにせよ、互いの意図は伝わったようで何よりです。(^^;


VB2010 ではなく、最近の環境であれば、
 Dim value1 As String = xmlDoc.SelectSingleNode("/Root/ID/text()")?.Value
 Dim value2 As String = xmlDoc.SelectSingleNode("/Root/ID/Name/text()")?.Value
という手も使えたかも知れません…。



> Textノードが列挙されて意味不明でしたが
> これで納得しました。
場合によっては CDATA セクション、コメント、処理命令などの
扱いも考える必要がありますが、今のところは大丈夫そうですね。


> 自分はほとんどLINQ式を使いますが、DOM式にしかできない事、又DOM式がLINQ式
> に比べて優位な部分があるのでしょうか?

XmlDocument は、CLR1 環境でも動作する、という程度の優位点ぐらいしか
思い当たらないですね。
こちらは W3C DOM Level 1 Core および Level 2 Core に準拠した
標準的な API で、古いバージョンの .NET Framework でも動作します。

一方、XDocument は、XLinq すなわち LINQ to XML のために
再設計されたもので、後発だけに使いやすくなっています。
IXmlLineInfo を通じて行番号と行位置を得ることもできますし、
XML 名前空間の扱いについても、こちらの方が扱いやすいかと。

https://docs.microsoft.com/ja-jp/dotnet/visual-basic/programming-guide/concepts/linq/linq-to-xml-vs-dom
https://blogs.msdn.microsoft.com/codejunkie/2008/10/08/xmldocument-vs-xelement-performance/


ただし、XPath を使う場合においては、私は DOM 版の実装である
SelectSingleNode / SelectNodes の方が好みです。

XLinq 版も、System.Xml.XPath 名前空間に、
XPathSelectElement(s) / XPathEvaluate の拡張メソッドが
用意されていますが、こちらは若干使いにくく感じています。


というのも、DOM 版の XPath は XmlNode を返すので
"/Root/ID/@id" や "/Root/ID/text()" を指定できるのですが、
XLinq 版の方は XNode ではなく XElement なので、
エラーになってしまうから…。
かといって XPathEvaluate だとレイトバインドになってしまうし。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2018/9/3 00:52:35
> XLinq 版の方は XNode ではなく XElement なので、

…と思ったけど、XPathNavigator 経由にしてしまえば良いのかな。
どっちにしても VB2015 未満だと、NULL条件演算子( ?. ) が使えないので扱いにくいですが。

Dim value1 As String = Doc.CreateNavigator().SelectSingleNode("/Root/ID/text()")?.Value
Dim value2 As String = Doc.CreateNavigator().SelectSingleNode("/Root/ID/Name/text()")?.Value
投稿者 mayopee  (社会人) 投稿日時 2018/9/3 14:52:48
魔界の仮面弁士様、返信ありがとうございます。 

XmlDocument版
>Dim value2 As String = xmlDoc.SelectSingleNode("/Root/ID/Name/text()")?.Value

XDocument版
>Dim value2 As String = Doc.CreateNavigator().SelectSingleNode("/Root/ID/Name/text()")?.Value

XPath式でTextノードを得るには、こんな書き方もできるのですね。
勉強になります。VB2010ですので、ご指摘通り「?.」の部分でコンパイラに怒られましたが
「?.」を消去すればTextノードがNothingでない要素については希望する結果を得られました。
NULL条件演算子( ?. )、とても便利そうですね!

>場合によっては CDATA セクション、コメント、処理命令などの

コメント以外、使った事も見た事もありません。CDATA セクションについては、Webで検索して
自分でXMLを作成する場合でも使えそうだと思いました。めったと、禁止文字を使う事はないけど
エスケープ文字はなんだっけ?となるよりいいかなという感想です。

DOM式とLINQ式についても、ご意見を頂き、ありがとうございます。
自分の場合、LINQ式を使う理由としてはXMLを読み込んで要素を編集、抽出する場合にLINQが
使えるXDocument/XElementの方が圧倒的に使い勝手が良い、コードも簡潔になって見通しが良い
という理由からです。
でも、今回ご指摘いただいたOfTypeやCastを使えばDOM式でもSelectやWhereが使えるのですね。

話題がちょっと脱線しましたが、当初の疑問は解決しましたので、これで解決とさせていだだきます。
私の発言に反応があるかもしれないので、解決マークはしばらく未チェックにしておきます。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2018/9/3 18:15:16
> コードも簡潔になって見通しが良いという理由からです。

XDocument を使った場合の利点として、XML IntelliSense のサポートも挙げられるかも。
C# にはない VB ならではの魅力ですね。

https://msdn.microsoft.com/library/bb531325%28vs.120%29.aspx



今回の質問にある XML に対応させると、XML スキーマ(*.xsd)はこんな感じかな…?

<xs:schema
  targetNamespace="urn:mayopee"
  attributeFormDefault="unqualified"
  elementFormDefault="qualified"
  xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="ID" maxOccurs="unbounded" minOccurs="0">
          <xs:complexType mixed="true">
            <xs:sequence>
              <xs:element name="Name">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element type="xs:byte" name="Age"/>
                  </xs:sequence>
                </xs:complexType>
              </xs:element>
            </xs:sequence>
            <xs:attribute type="xs:unsignedInt" name="id" use="required" />
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
投稿者 mayopee  (社会人) 投稿日時 2018/9/4 09:00:50
>XDocument を使った場合の利点として、XML IntelliSense のサポートも挙げられるかも。

おお、すばらしい。コーディング、めっちゃ楽ですね。

魔界の仮面弁士様、どうもありがとうございました。
これにて解決とさせていただきます。