LoginSignup
1
1

More than 5 years have passed since last update.

RDF/XMLデータモデルのSAXパーサによるパース、のメモ

Posted at

RDF/XMLのためのSAXパーサの実装(イベントハンドラの実装)をしたら大変だったのでメモ。
大変なので、可能な限りライブラリを使いましょう。たぶんrdf4jとかで大抵のことはできるような気がします。

将来の(色々なことを忘れたときの)自分用のメモなので、感想ポエムを含んだイントロになっています。
実際のソースコードは載っていません。再実装するときや実装を読み解く時のヒントとして。

イントロ

RDFとは

  • 有向グラフで表されるデータ構造。
  • 有向辺自体がPredicate、辺の始点がSubject、終点がObjectとそれぞれ呼ばれる
  • グラフを辺集合の形で、 <Subject,Predicate,Object> の対で表す
  • 頂点は「ノード要素」(名前がややこしいが、この記事では、グラフ理論上のノードは「頂点」と表記している。「ノード」とあるものは「ノード要素」を指している。)と「プロパティ要素」がある。
  • Subject は 必ずノード要素である
  • Object は ノード要素あるいはプロパティ要素がありうる
  • したがって (ノード要素) × (弧) × (ノード要素 or プロパティ要素) の集合としてデータを表している
  • ノード要素はIDを持つ
  • ノード要素のIDは明示的に与えられている場合と、そうでない場合があり、後者はblank node(一般的な訳語:空白ノード。「無名ノード」とかの方がJavaプログラマにはわかりやすい気がする)と呼ばれる

RDF/XML

  • 上記の (ノード要素) × (弧) × (ノード要素 or プロパティ要素) の集合をXMLで表したもの。
  • 基本は以下の2つで表される
Objectがノード要素.xml
<rdf:Description rdf:about="SubjectのノードID">
  <Predicate>
    <rdf:Description rdf:about="ObjectのノードID">
    </rdf:Description>
  </Predicate>
</rdf:Description>
Objectがプロパティ要素.xml
<rdf:Description rdf:about="SubjectのノードID">
  <Predicate>
    Objectのプロパティの値
  </Predicate>
</rdf:Description>
  • が、省略記法のルールが多く存在し、パースするのは大変である
  • (たぶん、RDFモデルを人間様が見てもわかりやすいように出力するために、さまざまな省略ができるようになっているように見え、パースするコンピュータ様(のためにパーサを書くためのレベル低めのプログラマ)には優しくない仕様、に見える。※レベル低めのプログラマの感想)

プログラムで扱うときに困ること

  • グラフ構造になっているので、DOM的な辿り方はできない
    • DOM的なXMLツリーの場合は、「HogeのFugaのPiyoが見たい(例:A部署のBさんの電話番号)」と思ったときに、ツリー化されたXMLであれば、 Hoge/Fuga/Piyo (/部署[@部署名=A]/社員[@氏名=B]/電話番号/text()) 的なものを取ってくればよいが、XMLとしてはツリー化されないので、HogeのID⇒FugaのIDを取ってきて、FugaのID⇒Piyoの値を取ってくる、みたいなプロセスを踏む必要がある。データ量が大きい場合は、ノード要素のIDでインデクス(ソート木でもハッシュテーブルでも)を作っておいて、検索できるようにしておかないと、常に全部走査することになって遅くなる
  • XMLとして等価でなくても、RDFとして(表現される構造)は等価でありうる
    • 省略記法がたくさんあってつらい

といったことがあるので、XMLそのままでは色々と都合が悪く、(ノード要素) × (弧) × (ノード要素 or プロパティ要素) の集合の形に書き直す必要がある。

SAXパーサで書く時のポイント

そんなわけで、SAXパーサで頭から読んでいって、確定した(ノード要素) × (弧) × (ノード要素 or プロパティ要素)の値を外(ファイルなりDBなり)に書き出していく。
SAXパーサ(のイベントハンドラ)を実装していくうえでのポイントは以下。

  • パーサに状態を持たせて、モードに合わせて状態遷移させる必要がある
    • rdf:parseType="Collection", rdf:parseType="Literal", rdf:parseType="Resource"
    • 特に指定のない場合に2パターン
  • ↑後者の、指定のない場合に2パターンいるのは、主語が読み込み終わっている場合(次の要素をPredicateとしてパースする)と、Predicateまで読みこみ終わっている場合(次の要素をObjectのノード要素あるいはプロパティ要素)で状態を分けるため。

状態は {Root, S1, S2, Collection, Resource, Literal} があって(S1はPredicateのパースが未了、S2はPredicateのパースが済んでいる)、エレメントが開くたびに状態が遷移する。状態遷移のたびに現在の状態をスタックに積む。エレメントが閉じた場合は、スタックからポップして状態を戻す。
エレメントが開いたときの状態遷移は以下。

Root  (+ 任意のエレメント)|-> S1
S1 (+ rdf:parseType="Collection|Resource|Literal" のエレメント)|-> Collection|Resource|Literal 
S1 (+ rdf:Resourceを含む=ノード要素 のエレメント)|-> S1
S1 (+ rdf:Resourceを含まないエレメント)|-> S2
S2 (+ 任意のエレメント)|-> S1
Resource (+ rdf:parseType="Collection|Resource|Literal" のエレメント)|-> Collection|Resource|Literal 
Resource (+ rdf:Resourceを含む=ノード要素 のエレメント)|-> S1
Resource (+ rdf:Resourceを含まないエレメント)|-> S2
Collection (+ 任意のエレメント)|-> S2

となる。

あとは読みかけの文(SubjectあるいはSubject×Predicate)と、上記の状態をスタックに積んで、PushしたりPopしたりしながら読んでいくと、SAXでちゃんとパースできる。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1