雑記
 

VB/C#でXMLの読み込み

2020/5/22

この記事が対象とする製品・バージョン

VS2019 Visual Studio 2019 対象です。
VS2017 Visual Studio 2017 対象です。
VS2015 Visual Studio 2015 対象です。
VS2013 Visual Studio 2013 対象です。
VS2012 Visual Studio 2012 対象です。
VS2010 Visual Studio 2010 対象です。
VS2008 Visual Studio 2008 対象ですが、一部サンプルプログラムはサンプル中で使用しているXML以外の機能が原因で動作しません。
VS2005 Visual Studio 2005 対象ですが、一部サンプルプログラムはサンプル中で使用しているXML以外の機能が原因で動作しません。
VS.NET 2003 Visual Studio 2003 対象ですが、多くのサンプルプログラムはサンプル中で使用しているXML以外の機能が原因で動作しません。
VS.NET 2002 Visual Studio (2002) × 対象外です。

目次

1.概要

この記事ではVB/C#でXMLを読み込む方法を説明します。

 

VB/C#でXMLを読み込んで処理をするには大きく分けて4つのアプローチがあります。

  1. VB/C#のクラスにマッピングする
  2. XPathで位置を指定して値を取り出す
  3. 先頭から順次読み込む
  4. クエリ(LINQ to XML)を使って値を取り出す

この記事では1・2・3のアプローチを扱います。3はさらに2つの方法に分けて扱います。簡単な使い分けは次の通りです。

(4のLINQ to XMLは洗練されていて便利ですが別の記事で説明します。)

アプローチ 適しているケース
1.VB/C#のクラスにマッピングする ・XMLの構造が事前にわかっている。
・XMLの中にあるたくさんの値を読み込む必要がある。
2.XPathで位置を指定して値を取り出す ・XMLの構造が事前にわかっている。
・XMLの中から取り出したい値は多くない。
3.先頭から順次読み込む XmlDocumentを使う方法 ・XMLの構造が事前にわかっていない。
・難しいプログラムを扱いたくない。
XmlReaderを使う方法 ・XMLの構造が事前にわかっていない。
・XMLのサイズが巨大
・パフォーマンスが重要
・難しいプログラムを扱っても良い。
4.クエリ(LINQ to XML)を使って値を取り出す ・XMLの構造が事前にわかっている。
・条件をつけて抽出したり、並び替えたり、集計したりする。

■表:XMLを読み込むアプローチの使い分け

知識がなさ過ぎてよくわからない場合は、アプローチ1から順番に試してみて、合わないと思ったら次のアプローチを試してみてください。

 

参考

Effective XML Part1: Choose the right API (効果的なXML 第1回:正しいAPIの選択)

https://docs.microsoft.com/ja-jp/archive/blogs/xmlteam/effective-xml-part-1-choose-the-right-api

 

 

この記事では題材として次の2つのサンプルXMLを読み込む場合を考えます。

サンプル1:シンプルなXML

<?xml version="1.0" encoding="utf-8" ?>
<history>
  <location>日本</location>
  <era name="奈良時代" start="710" end="794">
    <person name="坂上田村麻呂">
      <image>https://xxxxxx/x1.jpg</image>
      <note>征夷大将軍</note>
    </person>
  </era>
  <era name="平安時代" start="794" end="1185">
    <person name="藤原道長">
      <note>摂政。藤原家の摂関政治の絶頂。</note>
    </person>
    <person name="清少納言">
      <note>枕草子</note>
    </person>
  </era>
</history>

サンプル1は個人や企業で使用する独自のXMLを想定しており、XMLの要素と属性だけを使用したシンプルな構造です。

サンプル1の特徴

・属性で値が指定されているもの(「奈良時代」や「710」など)と要素の内容として値が指定されているもの(「日本」や「征夷大将軍」など)があります。

・era要素とperson要素は複数出現します。(プログラムで読み込むとコレクションか配列で表現されることになります。)

・XML名前空間(xmlns)が使用されていません。

 

サンプル2:名前空間のあるXML

<?xml version="1.0" standalone="yes"?>
<parent xmlns="http://example.org"
       
xmlns:svg="http://www.w3.org/2000/svg">
  <!-- parent contents here -->
  <svg:svg width="4cm" height="8cm" version="1.1">
    <svg:ellipse cx="2cm" cy="4cm" rx="2cm" ry="1cm" />
  </svg:svg>
</parent>

サンプル2は規格としてある程度共通で定義されているXMLを想定しています。この例はグラフィックスをXMLで表現する規格であるSVGです。

サンプル2の特徴

・XML名前空間(xmlns)が使用されています。

・コメントがあります。

 

 

2.準備

VBの場合、 この記事で紹介する例はプログラムの冒頭で以下のImports文が記述されていることが前提です。

Imports System.Xml.Serialization

Imports System.Xml

C#の場合、 この記事で紹介する例はプログラムの冒頭で以下のUsing文が記述されていることが前提です。

using System.Xml.Serialization;

using System.Xml;

 

このほかに、状況によっては追加のImportsまたはusingが必要な場合があります。

 

アプローチ1:VB/C#のクラスにマッピングする

基本的な使用方法

XMLの定義があらかじめわかっている場合はVB/C#で同じ構造のクラスを作成しておくと、そのクラスに値を割り当てることができます。クラスを定義するコツは後述します。

このアプローチの良い点はXMLとロジックを疎結合にしやすい点です。クラスに値が割り当てられた後は完全にVB/C#の世界の話になってXMLを意識しなくてよくなります。

 

サンプル1のXMLの内容をVB/C#で定義したクラスにマッピングして読み込む例を以下に示します。

読み込みの実行にはXmlSerializerクラスのDesrializeメソッドを使用します。XmlSerializerクラスのコンストラクターでマッピング先の型を指定します。

VB

 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Public Class history
    Public Property location As String
    <XmlElement("era")> Public Property era As List(Of era)
End Class

Public Class era
    <XmlElement("person")> Public Property person As person()
    <XmlAttribute> Public Property name As String
    <XmlAttribute> Public Property start As Integer
    <XmlAttribute> Public Property [end] As Integer
End Class

Public Class person
    Public Property image As String
    Public Property note As String
    <XmlAttribute> Public Property name As String
End Class

Private Function
LoadXmlSample1Text() As String

    Dim xml As New System.Text.StringBuilder

    xml.AppendLine("<?xml version=""1.0"" encoding=""utf-8"" ?>")
    xml.AppendLine("<history>")
    xml.AppendLine("  <location>日本</location>")
    xml.AppendLine("  <era name=""奈良時代"" start=""710"" end=""794"">")
    xml.AppendLine("    <person name=""坂上田村麻呂"">")
    xml.AppendLine("      <image>https://xxxxxx/x1.jpg</image>")
    xml.AppendLine("      <note>征夷大将軍</note>")
    xml.AppendLine("    </person>")
    xml.AppendLine("  </era>")
    xml.AppendLine("  <era name=""平安時代"" start=""794"" end=""1185"">")
    xml.AppendLine("    <person name=""藤原道長"">")
    xml.AppendLine("      <note>摂政。藤原家の摂関政治の絶頂。</note>")
    xml.AppendLine("    </person>")
    xml.AppendLine("    <person name=""清少納言"">")
    xml.AppendLine("      <note>枕草子</note>")
    xml.AppendLine("    </person>")
    xml.AppendLine("  </era>")
    xml.AppendLine("</history>")

    Return xml.ToString

End Function

Private Function TestXml() As history

    Dim xmlText As String = Me.LoadXmlSample1Text
    Dim deserializer As New XmlSerializer(GetType(history))
    Using stream As New MemoryStream(System.Text.Encoding.UTF8.GetBytes(xmlText))

        Dim result As history = DirectCast(deserializer.Deserialize(stream), history)
        Return result

    End Using

End Function

C#

public class history
{
    public string location { get; set; }
    [XmlElement("era")] public List<era> era { get; set; }
}

public class era
{
    [XmlElement("person")] public person[] person { get; set; }
    [XmlAttribute] public string name { get; set; }
    [XmlAttribute] public int start { get; set; }
    [XmlAttribute] public int end { get; set; }
}

public class person
{
    public string image { get; set; }
    public string note { get; set; }
    [XmlAttribute] public string name { get; set; }
}

private string LoadXmlSample1Text()
{
    StringBuilder xml = new StringBuilder();
    xml.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
    xml.AppendLine("<history>");
    xml.AppendLine("  <location>日本</location>");
    xml.AppendLine("  <era name=\"奈良時代\" start=\"710\" end=\"794\">");
    xml.AppendLine("    <person name=\"坂上田村麻呂\">");
    xml.AppendLine("      <image>https://xxxxxx/x1.jpg</image>");
    xml.AppendLine("      <note>征夷大将軍</note>");
    xml.AppendLine("    </person>");
    xml.AppendLine("  </era>");
    xml.AppendLine("  <era name=\"平安時代\" start=\"794\" end=\"1185\">");
    xml.AppendLine("    <person name=\"藤原道長\">");
    xml.AppendLine("      <note>摂政。藤原家の摂関政治の絶頂。</note>");
    xml.AppendLine("    </person>");
    xml.AppendLine("    <person name=\"清少納言\">");
    xml.AppendLine("      <note>枕草子</note>");
    xml.AppendLine("    </person>");
    xml.AppendLine("  </era>");
    xml.AppendLine("</history>");

    return xml.ToString();

}

private history TestXml()
{
    string xmlText = this.LoadXmlSample1Text();
    XmlSerializer deserializer = new XmlSerializer(typeof(history));

    using(var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(xmlText)))
    {
        history result = deserializer.Deserialize(stream) as history;
        return result;
    }
}

 

この例では、マッピングした後、何もしていませんが、result変数の内容を見ると、ちゃんと値が入っているのわかります。

ここまでできれば後はプログラムで自由に利用できますね。

 

 

名前空間の指定方法

サンプル2のXMLではXML名前空間(xmlns)が使用されているため、マッピング先のクラスで属性としてXML名前空間を示す必要があります。

VB

 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

<XmlRoot([Namespace]:="http://example.org")>
Public Class parent
    <XmlElement([Namespace]:="http://www.w3.org/2000/svg")> Public Property svg As svg
End Class

Public Class svg
    <XmlAttribute> Public Property width As String
    <XmlAttribute> Public Property height As String
    <XmlAttribute> Public Property version As String
    <XmlElement([Namespace]:="http://www.w3.org/2000/svg")> Public Property ellipse As ellipse
End Class

Public Class ellipse
    <XmlAttribute> Public Property cx As String
    <XmlAttribute> Public Property cy As String
    <XmlAttribute> Public Property rx As String
    <XmlAttribute> Public Property ry As String
End Class

Private Function
LoadXmlSample2Text() As String

    Dim xml As New System.Text.StringBuilder

    xml.AppendLine("<?xml version=""1.0"" standalone=""yes""?>")
    xml.AppendLine("<parent xmlns=""http://example.org""")
    xml.AppendLine("        xmlns:svg=""http://www.w3.org/2000/svg"">")
    xml.AppendLine("  <!-- parent contents here -->")
    xml.AppendLine("  <svg:svg width=""4cm"" height=""8cm"" version=""1.1"">")
    xml.AppendLine("    <svg:ellipse cx=""2cm"" cy=""4cm"" rx=""2cm"" ry=""1cm"" />")
    xml.AppendLine("  </svg:svg>")
    xml.AppendLine("</parent>")


    Return xml.ToString

End Function

Private Function TestXml() As parent

    Dim xmlText As String = Me.LoadXmlSample2Text
    Dim deserializer As New XmlSerializer(GetType(parent))
    Using stream As New MemoryStream(System.Text.Encoding.UTF8.GetBytes(xmlText))

        Dim result As parent= DirectCast(deserializer.Deserialize(stream), parent)
        Return result

    End Using

End Function

C#

[XmlRoot(Namespace="http://example.org")]
public class parent
{
    [XmlElement(Namespace="http://www.w3.org/2000/svg")] public svg svg { get; set; }
}

public class svg
{
    [XmlAttribute] public string width { get; set; }
    [XmlAttribute] public string height { get; set; }
    [XmlAttribute] public string version { get; set; }
    [XmlElement(Namespace="http://www.w3.org/2000/svg")] public ellipse ellipse { get; set; }
}

public class ellipse
{
    [XmlAttribute] public string cx { get; set; }
    [XmlAttribute] public string cy { get; set; }
    [XmlAttribute] public string rx { get; set; }
    [XmlAttribute] public string ry { get; set; }
}


private string LoadXmlSample2Text()
{
    StringBuilder xml = new StringBuilder();
    xml.AppendLine("<?xml version=\"1.0\" standalone=\"yes\"?>");
    xml.AppendLine("<parent xmlns=\"http://example.org\"");
    xml.AppendLine("  xmlns:svg=\"http://www.w3.org/2000/svg\">");
    xml.AppendLine("  <!-- parent contents here -->");
    xml.AppendLine("  <svg:svg width=\"4cm\" height=\"8cm\" version=\"1.1\">");
    xml.AppendLine("    <svg:ellipse cx=\"2cm\" cy=\"4cm\" rx=\"2cm\" ry=\"1cm\" />");
    xml.AppendLine("  </svg:svg>");
    xml.AppendLine("</parent>");

    return xml.ToString();

}


private parent TestXml()
{
    string xmlText = this.LoadXmlSample2Text();
    XmlSerializer deserializer = new XmlSerializer(typeof(parent));

    using(var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(xmlText)))
    {
        parent result = deserializer.Deserialize(stream) as parent;
        return result;
    }
}

 

 

 

XMLの定義をVB/C#のクラスに置き換えるコツ

Visual Studioの機能で自動的にXMLをVB/C#のクラスに変換することができます。経験や知識が足りない場合はこの機能を活用しましょう。

それには、対象のXML全体をコピーしてから、 Visual Studioの編集メニューから[形式を選択して貼り付け] - [XMLをクラスとして貼り付ける]を選択します。

ただし、この変換は機械的なものであり、必要以上に複雑なプログラムが生成されることが多いようで、属性などをどのように書けばよいのかわからない場合の参考とする使い方が良さそうです。私はこの機能で生成されるクラスをそのまま使用したことは1度もありません。

自分で書く場合のコツは次の通りです。

 

 

アプローチ2:XPathで位置を指定して値を取り出す

単一の値の取得

XMLの定義がわかっているならば、XPathを使うことで簡単にXMLの値を取得することができます。クラスを定義しなくてもよいのでピンポイントで値をとってくれば良い場合は便利です。

たとえば、次のようにしてサンプル1の坂上田村麻呂の画像のUrlを取得できます。(XMLに即して言えば、name属性が"坂上田村麻呂"であるperson要素の子要素imageの値を取得できます。)

このプログラム中に登場する LoadXmlSample1Textメソッドは上述のプログラム例に登場するものと同じなので割愛しています。

VB

 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim xmlText As String = Me.LoadXmlSample1Text
Dim document As New XmlDocument
document.LoadXml(xmlText)

Dim node As XmlNode = document.SelectSingleNode("//person[@name='坂上田村麻呂']/image")
Debug.WriteLine(node.InnerText)

C#

string xmlText = this.LoadXmlSample1Text();
XmlDocument document = new XmlDocument();
document.LoadXml(xmlText);

XmlNode node = document.SelectSingleNode("//person[@name='坂上田村麻呂']/image");
System.Diagnostics.Debug.WriteLine(node.InnerText);

 

SelectSingleNodeメソッドの引数に設定しているの文字列がXPathです。これで取得する対象を指定します。

XPathの説明はこちらに記載されています。

https://docs.microsoft.com/ja-jp/previous-versions/dotnet/netframework-4.0/ms256086(v=vs.100)

 

複数の値を取得

SelectNodesメソッドを使うと、XPathに該当する複数の属性や要素を1度に取得することもできます。

VB

VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim xmlText As String = Me.LoadXmlSample1Text
Dim document As New XmlDocument
document.LoadXml(xmlText)

Debug.WriteLine("■全personのname属性を列挙")
Dim personNames As XmlNodeList = document.SelectNodes("//person/@name")

For Each personName As XmlAttribute In personNames
    Debug.WriteLine("全人物:" & personName.Value)
Next

Debug.WriteLine("■平安時代のpersonのname属性とnote要素を列挙")
Dim heianPersons As XmlNodeList = document.SelectNodes("//era[@name='平安時代']/person")

For Each heianPerson As XmlElement In heianPersons
    Debug.WriteLine("平安時代の人物:" & heianPerson.GetAttribute("name"))
    Debug.WriteLine("ノート:" & heianPerson.SelectSingleNode("note").InnerText)
Next

C#

string xmlText = this.LoadXmlSample1Text();
XmlDocument document = new XmlDocument();
document.LoadXml(xmlText);

System.Diagnostics.Debug.WriteLine("■全personのname属性を列挙");
XmlNodeList personNames = document.SelectNodes("//person/@name");

foreach(XmlAttribute personName in personNames)
{
    System.Diagnostics.Debug.WriteLine("全人物:" + personName.Value);
}

System.Diagnostics.Debug.WriteLine("■平安時代のpersonのname属性とnote要素を列挙");
XmlNodeList heianPersons = document.SelectNodes("//era[@name='平安時代']/person");

foreach(XmlElement heianPerson in heianPersons)
{
    System.Diagnostics.Debug.WriteLine("平安時代の人物:" + heianPerson.GetAttribute("name"));
    System.Diagnostics.Debug.WriteLine("ノート:" + heianPerson.SelectSingleNode("note").InnerText);
}

 XPathで "//person" と記述すると、そのXML内に存在するすべての person要素 を選択できるので、XMLの構造が完全にわかっていなくても、取得したいものが明確であれば使用できます。

 

名前空間の指定方法

XML名前空間が使われているXMLの場合は、XmlNamespaceManagerクラスを使用してプレフィックスと名前空間の関連付けを指定する必要があります。次に例を示します。

この例はサンプル2のXMLを前提に、svg:ellipse要素を取得します。

VB

VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim xmlText As String = Me.LoadXmlSample2Text
Dim document As New XmlDocument
document.LoadXml(xmlText)

Dim nm As New XmlNamespaceManager(document.NameTable)
nm.AddNamespace("svg", "http://www.w3.org/2000/svg")

Dim node As XmlNode = document.SelectSingleNode("//svg:ellipse", nm)
Debug.WriteLine(node.OuterXml)

C#

string xmlText = this.LoadXmlSample2Text();
XmlDocument document = new XmlDocument();
document.LoadXml(xmlText);

XmlNamespaceManager nm = new XmlNamespaceManager(document.NameTable);
nm.AddNamespace("svg", "http://www.w3.org/2000/svg");

XmlNode node = document.SelectSingleNode("//svg:ellipse", nm);
System.Diagnostics.Debug.WriteLine(node.OuterXml);

 

XPath の式

参考に、MicrosoftのドキュメントからXPath式の例を転載します。

元ドキュメントは https://docs.microsoft.com/ja-jp/previous-versions/dotnet/netframework-4.0/ms256086(v=vs.100) です。このドキュメントにはもっと多くの情報があります。

このドキュメントがアーカイブされた領域にあり、そのうち消えてしまいそうな気がするのでここに抜粋するものです。

XPath式 説明
./author 現在のコンテキスト内のすべての <author> 要素。 これは、次の行の式に等しくなることに注意してください。
author 現在のコンテキスト内のすべての <author> 要素。
first.name 現在のコンテキスト内のすべての <first.name> 要素。
/bookstore このドキュメントのドキュメント要素 (<bookstore>)。
//author このドキュメント内のすべての <author> 要素。
book[/bookstore/@specialty=@style] ドキュメントのルートにある <bookstore> 要素の specialty 属性と同じ値の style 属性を持っているすべての <book> 要素。
author/first-name <author> 要素の子要素であるすべての <first-name> 要素。
bookstore//title <bookstore> 要素内の 1 段階以上深いレベル (任意の子孫) に含まれているすべての <title> 要素。 これは、次の行の式とは異なる点に注意してください。
bookstore/*/title <bookstore> 要素の孫要素であるすべての <title> 要素。
bookstore//book/excerpt//emph <bookstore> 要素内の任意の場所にある <book> 要素の子要素 <excerpt> 内の任意の場所にあるすべての <emph> 要素。
.//title 現在のコンテキスト内の 1 段階以上深いレベルにあるすべての <title> 要素。 この状況は、ピリオド表記が必須である場合において、基本的に 1 回だけ発生することに注意してください。
author/* <author> 要素の子要素であるすべての要素。
book/*/last-name <book> 要素の孫要素であるすべての <last-name> 要素。
*/* 現在のコンテキストのすべての孫要素です。
*[@specialty] specialty 属性を持つすべての要素。
@style 現在のコンテキストの style 属性。
price/@exchange 現在のコンテキスト内の <price> 要素の exchange 属性。
price/@exchange/total 属性には要素の子が含まれないため、空のノード セットが返ります。 この式は、XPath (XML Path Language) の文法上は使用可能ですが、厳密にいえば有効ではありません。
book[@style] 現在のコンテキストの style 属性を持つすべての <book> 要素。
book/@style 現在のコンテキストのすべての <book> 要素の style 属性。
@* 現在の要素のコンテキストのすべての属性。
./first-name 現在のコンテキスト ノード内のすべての <first-name> 要素。 これは、次の行の式に等しくなることに注意してください。
first-name 現在のコンテキスト ノード内のすべての <first-name> 要素。
author[1] 現在のコンテキスト ノード内の最初の <author> 要素。
author[first-name][3] 子要素 <first-name> を持っている 3 番目の <author> 要素。
my:book my 名前空間の <book> 要素。
my:* my 名前空間のすべての要素。
@my:* my 名前空間からのすべての要素 (これには、my 名前空間からの要素の修飾されていない属性は含まれません)。

 

アプローチ3:先頭から順次読み込む

XmlDocumentを使う方法(プログラムしやすい)

XMLの定義があらかじめわかっていない場合、For Each(foreach)の列挙や、条件分岐、再帰などを使って構造を解析したり、値を検索、取得していくことになります。

方法は大きく分けて2つあります。

1つはXmlDocumentを使う方法です。XMLの内容をすべてメモリ上に展開してから処理するためプログラムはやりやすいです。コメントの検出もできます。

もう1つはXmlReaderを使う方法です。XMLを先頭から順次読み込みながら解析する方法で、プログラムの難易度は高いですが、きめ細かい処理が可能で、コメントや閉じタグの検出もできます。メモリを節約でき高速に動作します。

 

まずはXmlDocumentを使う例を示します。

上述のサンプル1・サンプル2のXMLでのみテストした簡易的な実装であり、対応できないケースもあるのではないかと思いますが、構造がわからないXMLに対するアプローチを示すのには十分だと思います。

VB

VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Private Sub TestXml()
    Dim xmlText As String = Me.LoadXmlSample1Text
    Dim document As New XmlDocument
    document.LoadXml(xmlText)

    Debug.WriteLine("要素 " & document.DocumentElement.Name)
    Debug.IndentSize = 2
    Parse(document.DocumentElement)
End Sub

Private Sub Parse(node As XmlNode)

    Debug.IndentLevel += 1

    For Each attr As XmlAttribute In node.Attributes
        Debug.WriteLine("属性 " & attr.Name & " = " & attr.Value)
    Next

    For Each childNode As XmlNode In node.ChildNodes

        Select Case childNode.NodeType
            Case XmlNodeType.Element
                Debug.WriteLine("要素 " & childNode.Name)
                Parse(childNode)

            Case XmlNodeType.Text

                Dim value As String = childNode.Value
                If String.IsNullOrWhiteSpace(value) Then
                    'このサンプルでは空白または改行は無視します。
                Else
                    Debug.WriteLine(value)
                End If

            Case Else
                Debug.Write(childNode.NodeType.ToString & " ")
                Debug.WriteLine(childNode.OuterXml)

        End Select
    Next

    Debug.IndentLevel -= 1
End Sub

C#

private void TestXml()
{
    string xmlText = this.LoadXmlSample1Text();
    XmlDocument document = new XmlDocument();
    document.LoadXml(xmlText);

    System.Diagnostics.Debug.WriteLine("要素 " + document.DocumentElement.Name);
    System.Diagnostics.Debug.IndentSize = 2;
    Parse(document.DocumentElement);
}

private void Parse(XmlNode node)
{

    System.Diagnostics.Debug.IndentLevel += 1;

    foreach (XmlAttribute attr in node.Attributes)
    {
        System.Diagnostics.Debug.WriteLine("属性 " + attr.Name + " = " + attr.Value);
    }

    foreach (XmlNode childNode in node.ChildNodes)
    {
        switch (childNode.NodeType)
        {
            case XmlNodeType.Element:
                System.Diagnostics.Debug.WriteLine("要素 " + childNode.Name);
                Parse(childNode);
                break;

            case XmlNodeType.Text:
                string value = childNode.Value;
                if (string.IsNullOrWhiteSpace(value))
                {
                    //このサンプルでは空白または改行は無視します。
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine(value);
                }
                break;

            default:
                System.Diagnostics.Debug.Write(childNode.NodeType.ToString() + " ");
                System.Diagnostics.Debug.WriteLine(childNode.OuterXml);
                break;
        } //switch
    } //foreach

    System.Diagnostics.Debug.IndentLevel -= 1;
} //Parse

 

このプログラムを実行すると出力ウィンドウには次のように出力されます。

要素 history
  要素 location
    日本
  要素 era
    属性 name = 奈良時代
    属性 start = 710
    属性 end = 794
    要素 person
      属性 name = 坂上田村麻呂
    要素 image
      https://xxxxxx/x1.jpg
    要素 note
      征夷大将軍
  要素 era
    属性 name = 平安時代
    属性 start = 794
    属性 end = 1185
    要素 person
      属性 name = 藤原道長
      要素 note
        摂政。藤原家の摂関政治の絶頂。
    要素 person
      属性 name = 清少納言
      要素 note
        枕草子

 

XmlReaderを使う方法(速い・細かい制御ができる)

次にXmlReaderを使う例を示します。

サンプル1のXMLでは結果は上記のXmlDocumentの例と同じになります。サンプル2を対象にするとコメントの出力に少し違いがあることがわかります。

VB

VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim xmlText As String = Me.LoadXmlSample1Text
Dim settings As New XmlReaderSettings()

Debug.IndentSize = 2
Debug.IndentLevel = 0

Using stream As New MemoryStream(System.Text.Encoding.UTF8.GetBytes(xmlText))
    Using reader As XmlReader = XmlReader.Create(stream, settings)
        Do While reader.Read()
            Select Case (reader.NodeType)
                Case XmlNodeType.Element
                    Debug.WriteLine("要素 " & reader.Name)
                    Debug.IndentLevel += 1

                    If reader.HasAttributes Then
                        Do While reader.MoveToNextAttribute
                            Debug.WriteLine("属性 " & reader.Name & " = " & reader.Value)
                        Loop
                        reader.MoveToElement()
                    End If

                Case XmlNodeType.Text

                    Dim value As String = reader.Value
                    If String.IsNullOrWhiteSpace(value) Then
                        'このサンプルでは空白または改行は無視します。
                    Else
                        Debug.WriteLine(value)
                    End If

                Case XmlNodeType.EndElement '要素の終了(閉じタグなど)
                    Debug.IndentLevel -= 1

                Case XmlNodeType.Comment
                    Debug.WriteLine("Comment " & reader.Value)

            End Select
        Loop
    End Using
End Using

C#

string xmlText = this.LoadXmlSample1Text();
XmlReaderSettings settings = new XmlReaderSettings();

System.Diagnostics.Debug.IndentSize = 2;
System.Diagnostics.Debug.IndentLevel = 0;

using (var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(xmlText)))
{
    using (var reader = XmlReader.Create(stream, settings))
    {

        while (reader.Read())
        {
            switch (reader.NodeType)
            {
                case XmlNodeType.Element:
                    System.Diagnostics.Debug.WriteLine("要素 " + reader.Name);
                    System.Diagnostics.Debug.IndentLevel += 1;

                    if (reader.HasAttributes)
                    {
                        while (reader.MoveToNextAttribute())
                        {
                            System.Diagnostics.Debug.WriteLine("属性 " + reader.Name + " = " + reader.Value);
                        }
                        reader.MoveToElement();
                    }
                    break;

                case XmlNodeType.Text:
                    string value = reader.Value;
                    if (string.IsNullOrWhiteSpace(value))
                    {
                        //このサンプルでは空白または改行は無視します。
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine(value);
                    }
                    break;

                case XmlNodeType.EndElement: //要素の終了(閉じタグなど)
                    System.Diagnostics.Debug.IndentLevel -= 1;
                    break;

                case XmlNodeType.Comment:
                    System.Diagnostics.Debug.WriteLine("Comment " + reader.Value);
                    break;

            } //switch
        } //while
    } //using
} //using