XPathの動作確認(Excel VBA編)
目次
サンプルドキュメント
図1のXMLドキュメントファイルをVBAプログラムでロードして、XPathのテストを行う。 構造としては、Studentsの子要素にStudentが複数あり、さらにその子要素にIdとsubject別のScore要素がある。生徒・科目別の試験点数をイメージして作成したデータだ。
XPathの実装例
フルパス指定
' フルパス指定 Debug.Print doc.SelectSingleNode("/Students/Student/Id").text ' 20200025
上記コードを実行すると、イミディエイトウィンドウに「20200025」が出力される。コードの右端に出力値を載せている。Students の子要素に Student は複数あるが、特に指定がなければ最初の1つ目の要素が取得される。
@で属性を指定
' 属性値を指定 Debug.Print doc.SelectSingleNode("/Students/Student/Score[@subject='Science Math']").text ' 53 ' 「//」でルートから検索1 Debug.Print doc.SelectSingleNode("//Score[@subject='Science Math']").text ' 53 ' 「//」でルートから検索2 「*」ですべてのタグが対象 Debug.Print doc.SelectSingleNode("//*[@subject='Science Math']").text ' 53
subject属性が「Science Math」と一致する要素がヒットする。
containsの使い方
' containsの使い方1 Dim wkNode For Each wkNode In doc.SelectNodes("//Score[contains(@subject, 'Politics')]") Debug.Print wkNode.text ' 44, 92, 83 Next ' containsの使い方2 Debug.Print doc.SelectSingleNode("//Id[contains(text(),'202003')]").text ' 20200359
1では属性に対してcontainsを適用、2ではテキストノードに対して適用している。 1では、subject属性に「Politics」という文字列が含まれるScore要素がヒットする。2では、テキストノードに「202003」という文字列が含まれるID要素がヒットする。
position で要素の位置を指定
' 要素の位置を指定1 Debug.Print doc.SelectSingleNode("//Score[contains(@subject, 'Lang')][position()=2]").text ' 56 ' 要素の位置を指定2 Debug.Print doc.SelectSingleNode("//Score[contains(@subject, 'Lang')][2]").text ' 56 ' 要素の位置を指定3 For Each wkNode In doc.SelectNodes("//Student[3]/Score[position()>=2][position()<3]") Debug.Print wkNode.text ' 30, 33 Next
1では、まずsubject属性値に文字列「Lang」が含まれる要素がヒットする。テストデータを見ればわかるが、6個の要素がヒットする。この6個のうち、positionで指定された2番目が最終的にヒットする。 2は1の省略形。 3では、まず3番目のStudent要素の子要素Scoreのうち、2番目以上の要素 (図の赤色枠) がヒットする。その赤枠内での3番目未満 の要素 (図の青色枠) が最終的にヒットする。
sum, countの使い方
Debug.Print doc.SelectSingleNode("//Student[sum(Score/text()) div count(Score) > 80]/Id").text ' 20200186
当記事のテストデータは、生徒・科目別のテスト点数をイメージして作成したものだが、上記コードでは、平均点が80点以上の生徒のIDが取得される。
まず、sum(Score/text())について。Studentの子要素にはsubject別に5個のScore要素があり、さらに各Score要素のテキストノードに得点が表記されている。このテキストノードの値を全て集計している。 次に、count(Score)では、Studentの子要素Scoreの数をカウントしている。 div は除算を示す演算子だ。 結局、合計点数を科目数で割り算して平均値を算出し、計算結果が80以上のStudentを特定している。さらに、その特定されたStudentの子要素Idを取得している。
and, or の使い方
' and の使い方1 Debug.Print doc.SelectSingleNode("//Student[Score[contains(@subject,'Physics') and text() >= 80]]/Id").text ' 20200186 ' and の使い方2 For Each wkNode In doc.SelectNodes("//Student[2]/Score[position()>=3 and position()<5]") Debug.Print wkNode.text ' 95, 87 Next ' or の使い方 Debug.Print doc.SelectNodes("//Id[contains(text(),'202001') or contains(text(),'202002')]")(0).text ' 20200186
「and の使い方1」では、物理(Physics)の点数が80点以上の生徒のIDが取得される。
「and の使い方2」では、2番目のStudent要素の子要素Scoreのうち、3番目以上5番目未満の要素が取得される。上で説明済みのpositionのコード例と比べてほしいが、Score[position()>=3 and position()<5]
と Score[position()>=3][position()<5]
では意味が違うことに注意。
following-sibling の使い方
Debug.Print doc.SelectSingleNode("//Student[Id='20200359']/Score[3]/following-sibling::Score").text ' 54 Debug.Print doc.SelectSingleNode("//Student[Id='20200359']/Score[3]/following-sibling::Score[1]").text ' 54 Debug.Print doc.SelectSingleNode("//Student[Id='20200359']/Score[3]/following-sibling::Score[2]").text ' 83
following-sibling は、基点より後にある兄弟要素を検索する。
//Student[Id='20200359']/Score[3]
でヒットした要素を基点として、その後方の兄弟Score要素が検索される。なお、自分自身は含まれない。
following-sibling::Score
の末尾に position 指定がない場合は、ヒットした要素グループの先頭要素が取得される。 position 指定がある場合は、基点から後方に向かって position 番目の要素が取得される。
preceding-sibling の使い方
Debug.Print doc.SelectSingleNode("//Student[Id='20200359']/Score[3]/preceding-sibling::Score").text ' 80 Debug.Print doc.SelectSingleNode("//Student[Id='20200359']/Score[3]/preceding-sibling::Score[1]").text ' 30 Debug.Print doc.SelectSingleNode("//Student[Id='20200359']/Score[3]/preceding-sibling::Score[2]").text ' 80
preceding-siblingは、基点より前にある兄弟要素を検索する。
//Student[Id='20200359']/Score[3]
でヒットした要素を基点として、その前方の兄弟Score要素が検索される。なお、自分自身は含まれない。
preceding-sibling::Score
の末尾に position 指定がない場合は、ヒットした要素グループの先頭要素が取得される。 position 指定がある場合は、基点から前方に向かって position 番目の要素が取得される。
参考サイト
XPathについて初心者の場合は、まず以下サイトでイメージをつかむことをお勧めする。
https://www.techscore.com/tech/XML/XPath/index.html/
https://qiita.com/rllllho/items/cb1187cec0fb17fc650a