C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

XMLファイルの読み込み

[トピック内 6 記事 (1 - 6 表示)]  << 0 >>

■84721 / inTopicNo.1)  XMLファイルの読み込み
  
□投稿者/ なめこ (1回)-(2017/07/30(Sun) 09:19:56)

分類:[.NET 全般] 

今月からVB.NETをはじめた初心者です。
VisualStudio2017で開発を行っています。

設定ファイルに情報を保管して読み書きをして配列に保管しておきたいと考えております。
VB.NETではXML形式で行うと書いてあったので、
サイトのサンプルを利用しながらコードを書いてみました。
読み込みするところからはじめています。
  1)<Common>タブの取得方法がわからない
   2) <ReportNo> の下のノードをすべて取得したいのですが、
   最初のノードしか取得できない。(方法がわからない)

どなたかご教授ください。よろしくお願いいたします。
ご教授ください。

設定ファイルの中身
<Environment>
     <Common>
    <FileName>test.txt</FileName>
       <FileName1>test1.txt</FileName1>
     </Common>
     <ReportSET>
         <ReportNo kID="1">
             <ReptNo>0002</ReptNo>
             <ReportName>テスト</ReportName>
             <Orientation>2</Orientation>
         </ReportNo>   
         <ReportNo kID="2">
             <ReptNo>0003</ReptNo>
             <ReportName>フリー</ReportName>
             <Orientation>2</Orientation>
         </ReportNo>   
</Environment>


ソース
    Public Sub EnviromentFile_road()

        Dim xDocument As XmlDocument = New XmlDocument
        Dim xRoot As XmlElement
        Dim xDataList As XmlNodeList
        Dim xList As XmlNodeList
        Dim xNameList As XmlNodeList

        Call xDocument.Load(AppPath) 'XMLファイルをロード


        xRoot = xDocument.DocumentElement 'XMLドキュメントからルート要素を取り出す

        If xRoot IsNot Nothing Then
            Console.WriteLine(xRoot.Name)
        End If

        xDataList = xRoot.GetElementsByTagName("ReportSET") 'ルート要素から親リストを取得する


        For Each xElement As XmlElement In xDataList '親リストから親要素を取り出す
            xList = xElement.GetElementsByTagName("ReportNo") '親要素の中の子リストを取り出す

            For Each xNode As XmlElement In xList '子リストから子要素を取り出す
                Console.WriteLine(xNode.SelectSingleNode("@kID").Value) '子要素の属性を読み取る
                xNameList = xNode.GetElementsByTagName("ReptNo") '子要素から孫リストを取り出す

                For i As Integer = 0 To xNameList.Count - 1
                    Console.WriteLine(xNameList(i).InnerXml)
                Next
            Next xNode
        Next xElement
    End Sub

引用返信 編集キー/
■84722 / inTopicNo.2)  Re[1]: XMLファイルの読み込み
□投稿者/ WebSurfer (1274回)-(2017/07/30(Sun) 10:08:12)
No84721 (なめこ さん) に返信

> 設定ファイルに情報を保管して読み書きをして配列に保管しておきたいと考えております。
> VB.NETではXML形式で行うと書いてあったので、
> サイトのサンプルを利用しながらコードを書いてみました。

どこにどのように書いてあったのですか? 「情報」とは何か、どのように読み書きしたい
のか、もう少し具体的にやりたいことを書いていただけませんか?

「サイト」というのがネットに公開されているものなら URL とそのページのどこを参考にし
たか書いてください。

VB.NET はよくわかりませんが、.NET アプリなら Settings を利用できると思います。

Settings を使った WPF でのアプリケーション設定の保存
http://yohshiy.blog.fc2.com/blog-entry-253.html

デザイナで設定した値は app.config に xml 形式で保存されますが、プログラマが xml
を書く必要はないのですが・・・
引用返信 編集キー/
■84724 / inTopicNo.3)  Re[2]: XMLファイルの読み込み
□投稿者/ もりお (44回)-(2017/07/30(Sun) 19:51:28)
No84721 (なめこ さん) に返信

>   1)<Common>タブの取得方法がわからない

xRootが<Environment>で、<Common>は<Environment>の子ノードですよね
なので<Common>はこんな感じで取得できるかと思います。

Dim common As XmlNode = xRoot.GetElementsByTagName("Common")(0)


>    2) <ReportNo> の下のノードをすべて取得したいのですが、
>    最初のノードしか取得できない。(方法がわからない)

xListが<ReportNo>のリストで、xNodeが<ReportNo>ですよと
<ReportNo>の下のノードはChildNodesでいけるんじゃないでしょうか

For Each child As XmlNode In xNode.ChildNodes
    Console.WriteLine("{0} = {1}", child.Name, child.InnerXml)
Next

引用返信 編集キー/
■84729 / inTopicNo.4)  Re[1]: XMLファイルの読み込み
□投稿者/ 魔界の仮面弁士 (1363回)-(2017/07/31(Mon) 12:01:00)
No84721 (なめこ さん) に返信
>          </ReportNo>   
> </Environment

実際には </Environment> の前に『</ReportSET>』があるものとします。


>      <Common>
>     <FileName>test.txt</FileName>
>        <FileName1>test1.txt</FileName1>
>      </Common>

FileName の直前にある 3 つの全角スペースは、実際には存在しないものと受け取ります。
(FileName とは異なり、FileName1 の前には全角スペースが無いようで…)


> 1)<Common>タブの取得方法がわからない

今回、FileName と FileName1 のように、要素名が異なっていますが、
要素名は無視して、文字列配列として取り込む方が良いのでしょうか。

それとも、すべて異なる要素名が(おそらく連番で)振られるので、
要素名とファイル名のペアで管理される必要があるのでしょうか。

いずれにせよ、LINQ to XML を使うのが簡単かと思います。


Dim doc = XDocument.Load(AppPath)
Dim commons = doc.<Environment>.<Common>.Elements()

'[前者]ファイル名の配列として受け取る場合
Dim ary() As String = commons.Select(Function(x) x.Value).ToArray()

'[後者]「要素名とファイル名のペア」のコレクションとする場合
Dim dic = commons.ToDictionary(Function(x) x.Name, Function(x) x.Value)



> 2) <ReportNo> の下のノードをすべて取得したい

'LINQ を使って辿るついでに、結果を匿名型として受け取ってみます
Dim q = From reportNo In doc.<Environment>.<ReportSET>.<ReportNo>
        Select New With {
            Key .ID = CInt(reportNo.@kID),
            Key .No = reportNo.<ReptNo>.Value,
            .Name = reportNo.<ReportNo>.Value,
            .Orientation = CInt(reportNo.<Orientation>.Value)
        }

'ToArray() を使えば 配列に変換できますが、必ずしもその必要は無いでしょう
'Dim p = q.ToArray()
'If p.Length >= 2 Then
'    MsgBox(p(0).Name)
'    MsgBox(p(1).Name)
'End If

'列挙して内容を確認します
For Each reportNo In q
    Console.WriteLine("ID={0}", reportNo.ID)
    Console.WriteLine("No={0}", reportNo.No)
    Console.WriteLine("Name={0}", reportNo.Name)
    Console.WriteLine("Orientation={0}", reportNo.Orientation)
Next



> VB.NETではXML形式で行うと書いてあったので、

XML はテキストデータである上、データとしては冗長になりがちなので、
比較対象によっては、必ずしも XML が優位と言うわけでもなかったりします。
使い分けはケースバイケースですね。

ということで、「VB.NETではXML形式で行う」というのが
どこに書いてあった言葉なのかにもよりますが、恐らくは、
下記の 3 つの事情を鑑みての言葉かと推察します。

★1:従来の ini ファイルは使うべきでは無いので、別の保存方式が求められる
★2:appName.exe.config を使うことで、開発が簡単になる
★3:さらに VB は、「XML リテラル」という専用構文を供えている


---
★1 について

そもそも ini ファイルというものは、もともと Win16 向けの機能であるため、
Win32 以降においては非推奨とされています。その理由の一つとしては、
Program Files 配下は、一般ユーザーにとっては ReadOnly であり
書き込み権限を有していない可能性が高いなどの理由によるもので、
レジストリの利用が推奨されています。

その関係で、VB4 以降でも、Get/WritePrivateProfileString API は非推奨であり、
かわりに VB の標準機能として
 DeleteSetting ステートメント
 GetSetting 関数
 GetAllSettings 関数
 SaveSetting ステートメント
が用意されたという背景があります。API 宣言無しで使えるのがメリットです。
これらはレジストリに設定を保存する機能です。

なお上記関数群は、VB.NET でも引き継がれていますが、あくまでも互換機能です。
.NET においては My.Computer.Registry あるいは Microsoft.Win32.Registry を
用いることで、より細かいレジストリ制御が可能となっています。


とはいえ、レジストリではなくファイルでのやり取りが好まれるケースもあるので、
その場合は XML が使われたりします。
また、最近だと Json 形式が使われる場面も増えていますね。


たとえば Microsoft 製品でも、"Visual Studio Code" の設定ファイルは Json 形式ですし、
"Internet Information Services" の設定ファイルは XML 形式が使われています。



---
★2 について

これは No84722 で WebSurfer さんが書かれていた内容です。
プログラムからアクセスする場合は、My.Settings を使えば OK。

'アプリケーション設定は ReadOnly
Dim a1 = My.Settings.アプリ設定1
Dim a2 = My.Settings.アプリ設定2

'ユーザー設定アプリケーション設定は Writable
Dim u1 = My.Settings.ユーザー設定名1
Dim u2 = My.Settings.ユーザー設定名2

'編集した結果を .config に保存したり、再読み込みしたりもできる
'My.Settings.Save()
'My.Settings.Reload()
'My.Settings.Reset()


設定手順については、下記のチュートリアルが参考になるかと思います。
http://www.atmarkit.co.jp/fdotnet/vblab/vb2005m_03/vb2005m_03_01.html
http://www.atmarkit.co.jp/fdotnet/vblab/vb2005m_03/vb2005m_03_02.html
http://www.atmarkit.co.jp/fdotnet/vblab/vb2005m_03/vb2005m_03_03.html



---
★3 について

XmlDocument クラスの代わりに、
XDocument クラスを使って読み書きできます。
読み込みのサンプルについては、本投稿の冒頭に書いたサンプルコードをご覧ください。

また、「Imports System.Xml.XPath」を加えておけば、
下記のように XPath 式を使った問い合わせもできます。

Dim doc = XDocument.Load(AppPath)
For Each fileName In doc.XPathSelectElements("/Environment/Common/*")
    Console.WriteLine(fileName.Name.LocalName & "=" & fileName.Value)
Next

引用返信 編集キー/
■84796 / inTopicNo.5)  Re[2]: XMLファイルの読み込み
□投稿者/ なめこ (2回)-(2017/08/02(Wed) 20:53:54)
No84729 (魔界の仮面弁士 さん) に返信
> ■No84721 (なめこ さん) に返信
皆様ご返信いただきましてありがとうございました。
魔界の仮面弁士様のコメントが一番丁寧にわかりやすく解説いただきましたので
その方法でコーディングしてみました。
<Common>の下のノードは取得できましたが、
要素数分ループする際に要素数分処理されてしまいます。
コーディングが間違っているのでしょうか。
>            If dic.ContainsKey("ReportCnt") Then
>                GMX_IniImgFileName = dic("ReportCnt") ←ここに要素数分入る
>            End If
また<ReportNo>の下は取得できたのですが、
<ReportName>のみ取得できません。全角等が含まれているからでしょうか。
お手数ですが押していただけると助かります。

【XML】
 <Environment>
     <Common>
         <ReportCnt>11</ReportCnt>
     </Common>
     <ReportSET>
         <ReportNo kID="1">
             <ReptNo>0002</ReptNo>
             <ReportName>Aテスト</ReportName>
             <Orientation>2</Orientation>
           </ReportNo>
      </ReportSET>
</Environment>
ソース
Public RptNo(99) As String
Public RepReportName(99) As String

        Dim doc = XDocument.Load(AppPath)
        Dim Commons = doc.<Environment>.<Common>.Elements()
        Dim dic = Commons.ToDictionary(Function(x) x.Name, Function(x) x.Value)
        For i As Integer = 0 To dic.Count - 1
            If dic.ContainsKey("ReportCnt") Then
                GMX_IniImgFileName = dic("ReportCnt") 
            End If
        Next
        Dim q = From reportNo In doc.<Environment>.<ReportSET>.<ReportNo>
         Select New With {
             Key .ID = CInt(reportNo.@kID),
             Key .No = reportNo.<ReptNo>.Value,
             Key .Name = reportNo.<RepReportName>.Value,
             Key .Orientation = CInt(reportNo.<Orientation>.Value)
         }
        For Each reportNo In q
            RptNo(WX_i) = CStr(reportNo.No)
            RepReportName(WX_i) = reportNo.Name
            WX_i = WX_i + 1
        Next

引用返信 編集キー/
■84804 / inTopicNo.6)  Re[3]: XMLファイルの読み込み
□投稿者/ 魔界の仮面弁士 (1377回)-(2017/08/03(Thu) 12:38:25)
No84796 (なめこ さん) に返信
> For i As Integer = 0 To dic.Count - 1
>  If dic.ContainsKey("ReportCnt") Then
>   GMX_IniImgFileName = dic("ReportCnt") 
>  End If
> Next

ここでループさせる必要はありません。
そもそも、ループ変数 i が使われていないので、
全く同じことを繰り返すだけになってしまいます。


XML 内に /Environment/Common/ReportCnt が複数出力するのであれば、
そもそも ToDictionary が失敗します。(キー重複)

もしも /Environment/Common/ReportCnt が 0 個もしくは 1 個だけの場合は、
 GMX_IniImgFileName = doc.<Environment>.<Common>.<ReportCnt>.Value()
だけで十分です。

この表記の場合、ReportCnt が 0 個なら Nothing が入りますし、
ReportCnt が 2 個以上なら、最初に検出された要素が使われます。

複数の要素がある場合に位置が分かっているなら、
 GMX_IniImgFileName = doc.<Environment>.<Common>(0).<ReportCnt>(0).Value()
などのようにインデックスを付与することもできます。


仮に、ToDictionary した変数から拾うにしても、ループ無しで
 If dic.ContainsKey(要素名) Then
  GMX_IniImgFileName = dic(要素名)
 End If
だけで良いはずです。

もしも Dictionary 内の要素を列挙するということなら
For ではなく For Each を使って
 For Each entry In dic
  Console.WriteLine("要素名=" & entry.Key.LocalName)
  Console.WriteLine("値=" & entry.Value)
 Next
です。



> <ReportName>のみ取得できません。全角等が含まれているからでしょうか。
> Key .Name = reportNo.<RepReportName>.Value,

<ReportName> が <RepReportName> になっていますよ。

…って、先の私のコードも
 .Name = reportNo.<ReportNo>.Value,
になっていますね。申し訳ない。

ただしくは、
 .Name = reportNo.<RepReportName>.Value,
もしくは
 Key .Name = reportNo.<RepReportName>.Value,
ですね。



> Public RptNo(99) As String
> Public RepReportName(99) As String
管理データが 2 種だけならこれでも良いですが、項目数が多い場合は、
複数の配列を用意するのではなく、

 Public Class Report
  Public Property No As String
  Public Property Name As String
  Public Property Orientation As Integer
 End Class

こんな感じのクラスを用意しておくと対処しやすいかもしれません。


データの管理は、
 Public ReportSet As SortedDictionary(Of Integer, Report)
に対して
  ReportSet = New SortedDictionary(Of Integer, Report)(
   doc.<Environment>.<ReportSET>.<ReportNo>.ToDictionary(
    Function(n) CInt(n.@kID),
    Function(n) New Report() With {
     .No = n.<ReptNo>.Value,
     .Name = n.<ReportName>.Value,
     .Orientation = CInt(n.<Orientation>.Value)
    }
   )
  )
という感じで。

配列だと最初に最大要素数を確定せねばなりませんが、これなら
データ件数の制限もなくなります。
(読み込んだデータ件数は ReportSet.Count で得られます)


値を取り出すときは、
 x = ReportSet(1).Name
 y = ReportSet(1).No
です。

配列に戻したければ、
 RptNo = ReportSet.Select(Function(r) r.Value.No).ToArray()
 RepReportName = ReportSet.Select(Function(r) r.Value.Name).ToArray()
ともできます。

まぁ、いろいろな方法があるということで…。
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ