PostgreSQLでXMLを処理してみよう!(最終回)(1)
第6回 (最終回) PostgreSQLによるXML処理落ち穂拾い
-XML Schema検証アプリケーションとの連携、PostgreSQL のXML名前空間対応-
響 楽人
1. はじめに
2009年3月に連載第一回を掲載してから早いもので半年が経ちました。PostgreSQLでのXMLサポート機能についてはこれまでの連載でほぼ網羅しましたので、最終回となる今回はまず、PostgreSQLには組み込まれていないXML Schemaによるデータチェックの方法について解説します。また、XMLの重要な概念で、PostgreSQLも対応しているXML名前空間についても説明します。
2. XML Schema検証アプリケーションとPostgreSQLとの連携
XML Schema検証アプリケーションはPostgreSQLに組み込まれていないため、現状ではXML SchemaによるXMLデータの妥当性検証 (XMLデータのタグ付けがXML Schemaで定義したとおりに行われているかどうかの検証) は、外部の一般アプリケーションを使って行う必要があります。XML Schemaに対応したXMLプロセッサは数多く存在しますが、その中でも定評がありしかもフリーで利用できるApachプロジェクトのXerces (ザーセス) をこの記事では使います。以下に、このXercesを使ってXMLデータの妥当性検証を行う方法、Javaプログラムからプロセッサを呼び出し、妥当性検証を行ってからPostgreSQLのテーブルにXMLデータを格納する方法を紹介します。
2.1 XML Schemaとは何か、その必要性
これまで何回か登場したmeiboテーブルのwork_history列 (XML型) の内容として書いたXMLデータのタグ (要素) の構造を見てみると、最上位にwork-record要素があり、経歴ごとにcareer要素が繰り返され、career要素の子要素のperiodの下にはstart要素とend要素がある……という具合に階層的に構造化されています。
XML Schemaは、このようなXMLの階層構造に関する規則をあらかじめ定めるための規格です。事前に定めた階層構造に従って記述されたXMLデータであることが分かっていれば、特定のデータにたどり着く経路をXPathを使って的確に記述できます。
このようにXML Schemaを使えば、meiboテーブルのwork_history列でこれまで暗黙のうちにXMLデータの構造をルール化して使っていたものをXML Schemaという標準規格を使って定義することができます。これにより、データベースに格納しようとしているデータの構造が事前に定義したXML Schemaと合っているかどうかをアプリケーションを使ってチェックすることができるようになります。
2.2 XML Schema検証とXMLプロセッサ
XMLデータの記述内容を規定するためにW3Cが開発したスキーマ言語としては、XML1.0規格 (1998年制定) の中で規定されている「DTD (Document Type Definition:文書型定義)」と、その後DTDを発展させた規格として独立して規格化した「XML Schema」 (2001年制定) の二つがあります。現在主流となっているXML Schemaの大きな特徴は、DTDと比べ、要素の内容や属性値のデータ型を詳細に定義できること、XML名前空間の使用を前提に設計されていること、スキーマ自体がXMLで記述されている (タグを使って記述する) ことです。
ところで、XMLデータのチェックには2段階あるのでその点留意が必要です。たとえば、XMLデータをPostgreSQLに格納しようとしたとき、以下のようなエラーが返されることがあります。
testdb=# CREATE TABLE temp(a xml);
CREATE TABLE
testdb=# INSERT INTO temp(a) VALUES('<a>111<b>222</a>');
ERROR: invalid XML content
DETAIL: Entity: line 1: parser error : Opening and ending tag mismatch: b line 1 and a
<a>111<b>222</a>
^
Entity: line 1: parser error : Premature end of data in tag a line 1
<a>111<b>222</a>
^
Entity: line 1: parser error : chunk is not well balanced
<a>111<b>222</a>
これは、要素<b>に終了タグがないためXMLデータの書き方が基本的に間違っていることを示するエラーです。XML Schemaで事前に定義されたタグの構造に合わないということを示すエラーではありません (そもそもここでXML Schemaは使っていませんのでチェックを行うことができません) 。
このようにXMLデータのチェックには、XMLのタグ記述ルールに従っているかどうか (開始タグと終了タグの対がきちんと書かれているなど) のチェックと、スキーマと照らし合わせた構造のチェックの2つがあります。規格の用語を使うと、前者は「整形式XML文書 (well-formed XML document)」としてのチェック、後者は「妥当なXML文書 (valid XML document)」としてのチェックということになります。
※XML1.0規格では、上記のように、整形式XMLの「整形式」は「well-formed」、妥当なXMLの「妥当な」は「valid」という英語になっています。これを知っていると、上記ERRORメッセージの「invalid XML content」が「妥当なXMLではない」という意味を表しているように読めて、PostgreSQLがXMLSchemaによる検証も行っていると誤解するかもしれませんが、PostgreSQLにはXML Schemaによる検証機能は含まれていませんので注意して下さい。
XMLプロセッサは、このように「整形式XML文書 (well-formed XML document)」としてのチェックと「妥当なXML文書 (valid XML document)」としてのチェックの2つを行っていることになります。 (XMLプロセッサには、整形式XML文書としてのチェックまでしか行わないものもありますが、今回使用したXercesなどはXML Schemaによる妥当なXML文書のチェックまで行います。)
この様子を図にすると以下のようになります。
2.3. XML SchemaとXMLデータ
第3回から使用しているmeiboテーブルのwork_history列にあるXMLデータはこれまでタグの構造チェックを行わずに格納していましたので、今回はそのXMLデータを検証して入れるというシナリオを考えます。
まず、work_history列のデータ構造をXMLSchemaで表します。 (※XML Schemaの構文についてはここでは説明しませんので、XMLの参考書をご覧ください。)
XML Schemaファイル (workhistory.xsd)
<?xml version="1.0" encoding="Shift_JIS"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="work-record"> <xsd:complexType> <xsd:sequence> <xsd:element ref="career" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="career"> <xsd:complexType> <xsd:sequence> <xsd:element ref="period" minOccurs="1" maxOccurs="1"/> <xsd:element ref="content" minOccurs="1" maxOccurs="1"/> <xsd:element ref="role" minOccurs="1" maxOccurs="1"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="content" type="xsd:string"/> <xsd:element name="role" type="xsd:string"/> <xsd:element name="period"> <xsd:complexType> <xsd:sequence> <xsd:element ref="start" minOccurs="1" maxOccurs="1"/> <xsd:element ref="end" minOccurs="0" maxOccurs="1"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="start" type="xsd:string"/> <xsd:element name="end" type="xsd:string"/> </xsd:schema>
スキーマに沿ったXMLデータも用意します。これはmeiboテーブルにおける“生田靖”さん用のXMLデータです。
XMLデータ (record001.xml)
<?xml version="1.0" encoding="Shift_JIS"?>
<work-record xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="C:/pgsqlxml/workhistory.xsd">
<career>
<period>
<start>1970-04-01</start>
<end>1980-03-31</end>
</period>
<content>製造業向け購買管理システムの開発、大手流通業向け販売管理システム開発</content>
<role>プログラマー</role>
</career>
<career>
<period>
<start>1980-04-01</start>
<end>1985-03-31</end>
</period>
<content>金融機関向け社内Webシステム開発</content>
<role>プロジェクトリーダー</role>
</career>
<career>
<period>
<start>1985-04-01</start>
<end/>
</period>
<content>IT戦略部にて規格提案業務</content>
<role>マネージャー</role>
</career>
</work-record>
※今回使用したXMLデータの文字コードがShift_JISであることを示すため、先頭のXML宣言 (<?xml……) においてencoding="Shift_JIS"と指定しています。
2行目にあるwork-record要素の中のxsi:NonamespaceSchemaLocation属性で、先に作成したXMLSchemaファイルを指定しています。
これらのファイルを置くフォルダを一つを作成しておきます (この記事ではC:\pgsqlxmlとしました。後述のXercesでは、フォルダー名に日本語が使われているとデータを読み込めないというエラーが出る場合があるようですので注意して下さい。)
2.4 Xercesを使ったXML Schemaによる検証
XercesはオープンソースプロジェクトのApacheから無料で入手できるXMLプロセッサです。http://xerces.apache.org/に様々なバージョンのXercesが公開されていますが、この記事ではXerces2 Javaを使いました。準備として、ダウンロードしたフォルダを解凍してProgramFilesなどにおき、フォルダの中のxercesImpl.jarとxercesSamples.jarのフルパスを環境変数CLASSPATHに指定します。
CLASSPATHの設定方法は図2の通りです。
また、Xercesを使うにはJava環境も必要です。Java環境にはJDK (開発環境) とJRE (実行のみ) があります。Xerces自体はJREで動きますが、後でご紹介するXML Schemaによる検証とPostgreSQLへのデータ投入を一括して行うJavaのプログラムをコンパイルして実行するにはJDKが必要になります。
Xercesを使った単体での検証はPostgreSQLとは直接関係ありませんので、Windowsのスタートメニューからコマンド・プロンプトを起動し、「XML Schemaの説明」で作成したスキーマとXMLインスタンスを保存したフォルダへディレクトリを移動します。
C:\pgsqlxml フォルダへ移動する:
C:\>cd C:\pgsqlxml
xercesでrecord001.xmlファイルのスキーマチェックを実行する:
C:\pgsqlxml>java sax.Counter -v -s -f record001.xml
record001.xml: 156 ms (19 elems, 1 attrs, 0 spaces, 180 chars)
エラーが出ていませんので、XML Schemaによる検証で問題なかったということで、record001.xmlは妥当なXML文書であるということが確認できました。
2.5 XML Schemaによる検証が済んだXMLデータをPostgreSQLに格納する
前項でXercesを使って検証したrecord001.xmlをPostgreSQLのmeiboテーブルに格納しましょう。先ほどのコマンド・プロンプトは終了し、PostgreSQLのコマンド・プロンプトを開きます。
psqlでデータベースに接続します:
C:\Program Files\PostgreSQL\8.3\bin>psql testdb postgres Password for user postgres:
第3回から使っているmeiboテーブルの中身を一旦削除します:
testdb=# DELETE FROM meibo; DELETE 5
変数fooにrecord001.xmlの内容を展開して代入します(この手法については、後の“【解説】psqlの変数差し換え機能を利用したファイル挿入”を参照) :
testdb=# \set foo '''' `type C:\pgsqlxml\record001.xml` ''''
meiboテーブルにid,nameなどの必要なデータと、変数fooに代入したXMLデータを格納します:
testdb=# INSERT INTO meibo(id,name,department,age,email,work_history) VALUES(1031,'生田靖','IT事業統括部',45,'ikuta@abcd.com',:foo); INSERT 0 1
内容を確認してみます:
testdb=# SELECT work_history FROM meibo; work_history -----------------------------------------------------------------------------------\r <work-record xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamesp aceSchemaLocation="C:/pgsqlxml/workhistory.xsd">\r <career>\r <period>\r <start>1970-04-01</start>\r <end>1980-03-31</end>\r </period>\r <content>製造業向け購買管理システムの開発、大手流通業向け販売管理システム開発</content>\r <role>プログラマー</role>\r </career>\r <career>\r <period>\r <start>1980-04-01</start>\r <end>1985-03-31</end>\r </period>\r <content>金融機関向け社内Webシステム開発</content>\r <role>プロジェクトリーダー</role>\r </career>\r -- More --
【解説】psqlの変数差し換え機能を利用したファイル挿入
XMLデータのファイルrecord001.xmlの内容を展開して挿入するのにpsqlツールの\setコマンドを利用した「SQL差し替え」と呼ばれる変数差し替え機能を使うことができます。セットされた変数の値の呼び出しには変数名の先頭に「:」をつけます。ファイルの内容をコピーする場合は以下のように使います。
\set content '''' `type my_file.txt` '''' INSERT INTO my_table VALUES (:content);
※Windows環境では上記のようにtypeですが、Linuxの場合はcatです。この機能ついて詳しくは PostgreSQL 文書「psql」の「高度な機能」の中の「変数」および「SQL差し替え」の項を参照してください。
2.6 XML Schemaによる検証からPostgreSQL格納までの一括処理―Javaプログラムを使って―
ここまでの説明では、手作業でXML Schema検証を行い、それをPostgreSQLに格納しましたが、このような処理を何度も繰り返し行うのは面倒です。そこで、XML Schemaによる検証を行ったXMLデータをPostgreSQLに格納するまでの処理を一括して行うJavaプログラムを書くことにします。
このプログラムを実行するのに必要な環境として、Java開発環境 (JDK) 、Xerces、JavaからPostgreSQLに接続するためのJDBCドライバが必要です。
皆さんのコンピュータにJDKが入っていない場合には、まずはJDKをインストールして環境設定 (PATH環境変数の設定) を行ってください。
JDBCドライバについては、PostgreSQL のJDBC Driverというサイト (http://jdbc.postgresql.org/download.html) から入手できます (この記事では「JDBC4 Postgresql Driver, Version 8.4-701」を使いました) 。ここからダウンロードされるjarファイルを適当なフォルダに置き (例えばC:\postgresql-8.4-701.jdbc4.jar) 、CLASSPATH環境変数に追加します (「2.4 Xercesを使ったXML Schemaによる検証」のCLASSPATH設定方法と同様) 。
Xercesによる検証とPostgreSQLへの挿入を行うJavaプログラム (validateandinsert.java)
import javax.xml.parsers.*; import org.xml.sax.*; import java.sql.*; import java.io.*; public class validateandinsert { public static void main(String[] args) { try { // スキーマ検証を行うメソッドの呼び出し vaildation(args[0]); // XMLファイルを読み込んで、文字列にする String xmlstring = xmlfileopen(args[0]); // XMLファイルをPostgreSQLに挿入する insert(xmlstring); } catch (Exception e) { e.printStackTrace(); } } /** * XMLファイルのスキーマ検証を行う。SAXを使用したスキーマ検証。 * @param xmlfile XMLファイルのフルパス */ public static void vaildation(String xmlfile) { try { // SAXをインスタンス化する SAXParserFactory saxfa = SAXParserFactory.newInstance(); XMLReader xmlr = saxfa.newSAXParser().getXMLReader(); // スキーマ検証 (XML Schema) を行うための、機能設定をSAXインスタンスに行う xmlr.setFeature("http://xml.org/sax/features/validation", true); xmlr.setFeature("http://apache.org/xml/features/validation/schema",true); xmlr.setFeature("http://xml.org/sax/features/namespaces",true); // XMLファイルの検証を実行する xmlr.parse(xmlfile); } catch (SAXParseException e) { System.out.println(e.getMessage()); } catch (Exception e) { e.printStackTrace(); } } /** * XMLファイルのを読み込んで、文字列として扱う * @param xmlfile XMLファイルのフルパス */ public static String xmlfileopen(String xmlfile){ try { // ファイルをBufferedReaderで開く BufferedReader in = new BufferedReader(new FileReader(xmlfile)); String s; String xmlstring = ""; // ファイル内容を読みこんで文字列にする while ((s = in.readLine()) != null) { xmlstring = xmlstring + s; } in.close(); return xmlstring; } catch (Exception e) { e.printStackTrace(); return "aaa"; } } /** * XMLファイルをPostgreSQLに挿入する * @param xmlstring XMLの文字列 */ public static void insert(String xmlstring){ try { // PostgreSQLのドライバクラスをロードする Class.forName("org.postgresql.Driver"); //データベースへの接続 Connection con = DriverManager.getConnection ("jdbc:postgresql:testdb","postgres","**設定したパスワード**"); // Insert文を作成する Statement stmt = con.createStatement(); String sql = "INSERT INTO meibo(id,name,department,age,email,work_history) VALUES(1031,'生田靖','IT事業統括部',45,'ikuta@abcd.com','"+ xmlstring + "');"; // Insert文を実行 int result = stmt.executeUpdate(sql); stmt.close(); con.close(); } catch (Exception e) { e.printStackTrace(); } } }
実行方法は以下の通りです。
C:\WINDOWS>cd C:\pgsqlxml
C:\pgsqlxml>javac validateandinsert.java
⇒フォルダに「validateandinsert.class」というファイルができる
C:\pgsqlxml>java validateandinsert record001.xml
⇒何もエラーが出なければ成功
testdb=# SELECT work_history FROM meibo;
⇒先ほど「2.5 XML Schemaによる検証を行ったXMLをPostgreSQLに格納する」で入れたデータと合わせて2件表示されればOK.
- validateandinsert.javaをPostgreSQLに格納したいXMLファイルと同じフォルダに置く
- validateandinsert.javaをテキストエディタで開いて、最後の方にある「// データベースへの接続」の引数を自分の環境に合わせる (通常psqlでPostgreSQLにアクセスするときと同じものを入力する)
- 新しくコマンド・プロンプトを開き、XMLファイルとJavaファイルのあるフォルダへ移動する
- javaファイルをjavacコマンドでコンパイルする
- 対象のXMLファイルを引数にしてプログラムを実行する
- PostgreSQLにpsqlで接続し、指定したXMLファイルがデータベースに格納されているかどうか確認する
work_history
-------------------------------------------------------------------------- \r
<work-record xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="C:/pgsqlxml/workhistory.xsd">\r
<career>\r
<period>\r
<start>1970-04-01</start>\r
<end>1980-03-31</end>\r
</period>\r
<content>製造業向け購買管理システムの開発、大手流通業向け販売管理システム開発</content>\r
<role>プログラマー</role>\r
</career>\r
<career>\r
<period>\r
<start>1980-04-01</start>\r
<end>1985-03-31</end>\r
</period>\r
<content>金融機関向け社内Webシステム開発</content>\r
<role>プロジェクトリーダー</role>\r
</career>\r
<career>\r
<period>\r
<start>1985-04-01</start>\r
<end/>\r
</period>\r
<content>IT戦略部にて規格提案業務</content>\r
<role>マネージャー</role>\r
</career>\r
</work-record>\r
<work-record xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="C:/pgsqlxml/workhistory.xsd"><career> <period>
<start>1970-04-01</start> <end>1980-03-31</end> </period> <content>製造業向け購買管理
システムの開発、大手流通業向け販売管理システム開発</content> <role>プログラマー</role></career>
<career> <period> <start>1980-04-01</start> <end>1985-03-31</end> </period> <content>
金融機関向け社内Webシステム開発</content> <role>プロジェクトリーダー</role></career><career>
<period><start>1985-04-01</start> <end/> </period> <content>IT戦略部にて規格提案業務
</content> <role>マネージャー</role></career></work-record>
(2 rows)