[nim]XMLをパース(parse)する

広告

nimを使ってXMLファイルをパースする為の日本語情報があまりないので、「xmlparser」モジュールを使ってのXMLファイルのパースについて記事にします
色々苦労しましたが、なんとか思い通りの動きができるようになりました

xmlparserモジュールの仕様

以下のリンクにて説明があります(英語)
XMLParserの仕様


使用例

といっても、所詮英語…一応以下のような仕様です

xmlをロードする

import os, streams, parsexml, strutils

var filename = "something.xml"
var s = newFileStream(filename, fmRead)
if s == nil: quit("cannot open the file " & filename)
var x: XmlParser
open(x, s, filename)

XmlParserがFileStreamをオ−プンしています

ノードを読み込む

xmlparserは、nextで次のノードに移ります
ループさせて、EOFまで読み込みます
xp.kindによって処理を分岐させる形になります

  xp.next
  block mainLoop:
    while true:
      case xp.kind
      of xmlElementStart:
		echo "element Start!"
		xp.next
      of xmlElementOpen:
		echo "element Open!"
		xp.next
      of xmlEof: break # end of file reached
      of xmlError:
        echo(errorMsg(xp))
        xp.next()
      else:
        xp.next() # skip other events
 

上記の例だと、EOF(xmlEof)まで無限ループを回して、xmlEofを見つけたら、ブレイクするという流れになります

kindは、以下のようなものがあります。よく使うものから順に紹介すると以下の通り

kindの名前 内容
xmlElementStart XMLの構成要素の開始を示します(”>”までを取得

 < elem >
xmlElementEnd XMLの構成要素の終了そ示す(”>”までを取得

 < /elem >
xmlElementOpen XMLの構成要素の開始を示す(”>”までを取得しない

 < /elem
xmlElementClose Openで取得した構成要素の”>”を取得する

 >
xmlAttribute Openで取得した構成要素の中にあるアトリビュートを取得

 key=”value”
xmlCharData 文字列データ

 テキスト
xmlError エラーが起きたときの

N/A
xmlEOF ファイルの終了

N/A

詳細は、以下の英語の説明にあるのですが、とりあえず上記さえあれば、なんとかコントロールできます

xmlElementStartとxmlElementOpenの違い

実は、最初この2つの使い方が理解できなかったのですが、基本的には以下のような違いのようです
例えば、以下のようなXMLファイルがあったとします


  
    劇団名
    
      
        公演名a<title>
        <fromdate>2018/01/01<fromdate>
        <todate>2018/01/3</todate>
      </show>
      <show id="bbb">
        <title>公演名b
        2018/02/01
	2018/02/28
      
     
  

この場合に、kind==xmlElementStartで得られた構成要素の名前(elementName)を抽出すると以下のように取れます

取得するソースは、以下の通り

import os, streams, parsexml, strutils

if paramCount() < 1:
  quit("Usage: htmltitle filename[.html]")

var filename = addFileExt(paramStr(1), "xml")
var s = newFileStream(filename, fmRead)
if s == nil: quit("cannot open the file " & filename)
var x: XmlParser
open(x, s, filename)
while true:
  x.next()
  case x.kind
  of xmlElementStart:
    echo x.elementName

  of xmlEof: break # end of file reached
  else: discard # ignore other events

x.close()

上記のソースコードで実行結果は、以下のようになります

xml
company
name
shows
title
fromdate
todate
title
fromdate
todate

見ていただくと気づきますが、showは、抽出されていません。これは、showはアトリビュート「id」を保有しているためです

アトリビュートを保有する項目は、「xmlElementOpen」で分岐する必要があります

上記のコードを以下のように修正して実行します

import os, streams, parsexml, strutils

if paramCount() < 1:
  quit("Usage: htmltitle filename[.html]")

var filename = addFileExt(paramStr(1), "xml")
var s = newFileStream(filename, fmRead)
if s == nil: quit("cannot open the file " & filename)
var x: XmlParser
open(x, s, filename)
while true:
  x.next()
  case x.kind
  of xmlElementOpen:
    echo "open:" & x.elementName
  of xmlElementStart:
    echo "start:" & x.elementName

  of xmlEof: break # end of file reached
  else: discard # ignore other events

x.close()

上記のコードの実行結果は以下のとおりです

start:xml
start:company
start:name
start:shows
open:show
start:title
start:fromdate
start:todate
open:show
start:title
start:fromdate
start:todate

この通り、この「xmlElementOpen」と「xmlElementStart」を両方キャッチしないとXMLの構成要素を全て取得することができません

要素のテキストを取得するのは、kindがCharDataであるときに取得する

上記のように、要素を特定したあとは、その要素の内容を取得します

  xp.next()  #xmlparserの次へノードを動かす
  if xp.kind==xmlCharData:  #次のノードが文字列データであることを確認
    value = xp.charData #文字列であれば、値に設定する
  else:
    value = "" 

上記のコマンドの組み合わせで、取り込めるようになりました

以上 nimでXMLファイルをパースして情報を取り込むための初歩の記事でした

PSちなみに、同様の処理をRubyとnimで実装した場合の速度比較は以下

3248のXMLファイルのパース処理

Ruby:18分4秒(4320秒)

nim :13秒

という圧倒的な差。332倍のスピード!!

ま、完全に同じ処理というわけではないのですが、それにして圧倒的です


広告

1 個のコメント

  • どうでもいいけど、「パース」じゃなくて「パーズ」ですよね。

  • コメントを残す

    メールアドレスが公開されることはありません。