今回はXMLファイルの読み込みについて解説いたします。
XMLファイルを取り込みたい方は、この記事を一読していただけると幸いです。
はじめに
XMLファイルの読み込みは、クラス「%XML.TextReader.cls」の関数「ParseFile」を使用します。
CSVファイルの読み込みとは異なり、かなり癖が強いです。
サンプルとして下記XMLファイルを用意しました。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<XmlMsg>
<MessageList>
<Person no="10000000" birthday="1980-09-02">藤原道長</Person>
<Person no="10000001" birthday="2021-09-02">紫式部</Person>
</MessageList>
</XmlMsg>
読み込み
CSVファイルを読み込む処理が、下記サンプルです。
関数「ParseFile」に対しXMLファイルの渡し、オブジェクトを受け取ります。
後は、取得したオブジェクトから「エレメント情報」を取得します。
ClassMethod main(filePath As %String) As %Status
{
s sts = $$$OK
try{
$$$ThrowOnError(##class(%XML.TextReader).ParseFile(filePath, .obj))
while (obj.Read()){
w !,obj.NodeType,",",obj.Name,",",obj.Value
}
}catch e {
s sts = e.AsStatus()
}
q sts
}
では、読み込んでみましょう。
下記コマンドをターミナルから実行します。
d ##class(developer.xml.Sample).main("D:\Temp\xml\Sample.xml")
実行結果です。
・・・癖が強い。
各エレメント毎に取得しているため、開始エレメント「element」と終了エレメント「endelement」が別々で取得しています。
また、エレメントのデータ部「藤原道長」「紫式部」は、エレメント「chars」で取得できています。
おまけに、「no」「birthday」が取得できていません。
う~ん
javascriptで取得するHTML DOMのような仕様にしてもらえませんかね・・・
あまりにも使い勝手が悪すぎる!
一部修正をする
あくまで一例ではありますが、狙ったデータを取得するため一部修正しています。
先ずは、エレメント「Person」よりAttribute値「no」「birthday」の取得を行っています。
後はエレメント「chars」より、人物名を取得しています。
ClassMethod main(filePath As %String) As %Status
{
s sts = $$$OK
try{
s text = ""
$$$ThrowOnError(##class(%XML.TextReader).ParseFile(filePath, .obj))
while (obj.Read()){
i (obj.NodeType = "element")&&(text="")&&(obj.Name = "Person"){
s:(obj.MoveToAttributeName("no")) text = obj.Value
s:(obj.MoveToAttributeName("birthday")) text = text_","_obj.Value
}
i (text'="")&&(obj.NodeType = "chars"){
s text = text_","_obj.Value
w !,text
}
i (obj.NodeType = "endelement")&&(text'="")&&(obj.Name = "Person"){
s text = ""
}
}
}catch e {
s sts = e.AsStatus()
}
q sts
}
では、読み込んで実行結果を確認しましょう。
一先ず、必要なデータは取得できたようです。
Attribute値に関しては、決め打ちで取得していましたが、汎用的に記述するなら下記方法もあります。
ClassMethod main(filePath As %String) As %Status
{
s sts = $$$OK
try{
s text = ""
$$$ThrowOnError(##class(%XML.TextReader).ParseFile(filePath, .obj))
while (obj.Read()){
i (obj.NodeType = "element")&&(text="")&&(obj.Name = "Person"){
k attrDt
f pos = 1:1:obj.AttributeCount {
d obj.MoveToAttributeIndex(pos)
s attrDt(obj.LocalName)=obj.Value
}
s text=1
}
i (text)&&(obj.NodeType = "chars"){
s text = attrDt("no")_","_obj.Value_","_attrDt("birthday")
w !,text
}
i (obj.NodeType = "endelement")&&(text'="")&&(obj.Name = "Person"){
s text = ""
}
}
}catch e {
s sts = e.AsStatus()
}
q sts
}
グローバルで取得する
データ取得用のPGを記述するのが手間だと感じたら、一旦グローバルに展開するのも一つの手になります。
最後の引数にグローバル名を文字列で渡すと、その中にXMLデータを詰めて返してきます。
下記がサンプルになります。
※グローバル名の初期値は「^||IRIS.Temp」です。
ClassMethod main(filePath As %String) As %Status
{
s sts = $$$OK
try{
s text = ""
$$$ThrowOnError(##class(%XML.TextReader).ParseFile(filePath, .obj,,,,,,$na(^||sample)))
zw ^||sample
}catch e {
s sts = e.AsStatus()
}
q sts
}
では、実行してみます。
SAMPLE>d ##class(developer.xml.Sample).main("D:\Temp\xml\Sample.xml")
^||sample=3
^||sample(3,1)=$lb("element","","XmlMsg","XmlMsg",0,1)
^||sample(3,2)=$lb("element","","MessageList","MessageList",0,2)
^||sample(3,3)=$lb("element","","Person","Person",2,3)
^||sample(3,3,1)=$lb("","no","no","CDATA","10000000")
^||sample(3,3,2)=$lb("","birthday","birthday","CDATA","1980-09-02")
^||sample(3,4)=$lb("chars","藤原道長","")
^||sample(3,5)=$lb("endelement","","Person","Person")
^||sample(3,6)=$lb("element","","Person","Person",2,3)
^||sample(3,6,1)=$lb("","no","no","CDATA","10000001")
^||sample(3,6,2)=$lb("","birthday","birthday","CDATA","2021-09-02")
^||sample(3,7)=$lb("chars","紫式部","")
^||sample(3,8)=$lb("endelement","","Person","Person")
^||sample(3,9)=$lb("endelement","","MessageList","MessageList")
^||sample(3,10)=$lb("endelement","","XmlMsg","XmlMsg")
グローバルに展開されれば、何となく取りやすくなった感じがする・・・ような?
ちょっとだけですかね(笑
Attribute値に関しては、断然分かりやすくなった感じがします。
XMLファイル読み込みの手段として、検討してみてください。
おわりに
XMLファイルは、CSVと異なり力技で解決するのが難しいファイルになります。
そのためパーサーに頼るのが一番ですが、IRIS/Cacheのパーサーはかなり癖が強いですよね。
この記事を読んで、XMLファイルの読み込みがスムーズにできる事を願っています。