Last-modified: 2003-12-15 (月) 16:39:20 (7437d)

ネイティブXMLデータベース ~Xindice~

2002.10.29
Jomora
 

はじめに

 さまざまな方面でXMLが利用されるようになり、その広まりとともに、XMLデータベースに対する注目も集まりつつあります。

 一般に、「XMLに対応したデータベース」というと、「XML文書をストアする機能を備えたデータベース」のことを指すようです。OracleIBM DB2 UDBなど、著名なリレーショナルデータベースも「XMLに対応」していますが、ここでご紹介する「ネイティブXMLデータベース」は、それらとは異なり、XMLを加工することなく「そのまま」保存、オリジナルと同じ状態で取り出せるデータベースのことです。

 本文では、ネイティブXMLデータベースと、現在のリレーショナルデータベースとの設計面、実装面での違いを中心に紹介していきたいと思います。

リレーショナルか、それともXMLネイティブか

 今後、ビジネスでやりとりされる文書は、ますますXML化されていくでしょう。大量のXML文書の保存と管理のために、XML対応のデータベースが必須となります。

 現在のところ、「XML対応のデータベース」製品は、2種類に大別できます。

  1. リレーショナル形式にマッピングして保存するデータベース
  2. XML文書をそのまま保存できるデータベース

 どちらも一長一短がある、というのが現状です。DB選択の際には、それぞれの特徴を考えて、要求に合う方を選ぶ必要があります。

XML対応のリレーショナルデータベース

relational.gif

 XML対応のリレーショナルデータベースは、XML文書をリレーショナル形式にマッピングして保存します。つまり、XML文書の決められた部分を特定のテーブルの特定のフィールドのデータにマッピングすることで、XML文書の中のデータをデータベースに格納するのです。当然、データベースに格納されているデータをXML文書に組み立てて出力する機能も持ちます。

 この方式の特徴は、まさにこの「XML要素を抜き出して保存する」点にあります。

  • 保存後は通常のDBデータとして扱えるため、高速なトランザクション処理、排他制御、バックアップ機能など、実績あるリレーショナルデータベースの機能を利用できる。
  • マッピングの方法は製品によってまちまちである。
  • XML文書から要素が抜き取られて保存されることになるため、元のXML文書の構造はいったん破棄される。
  • XML文書には、漢字コードや名前空間の情報などが付加されているし、要素の前後関係や親子関係なども意味を持っているが、列にマッピングされて保存された段階で、こうした情報は失われてしまう。
  • そのため、リレーショナルデータベースに格納された情報から元のXML文書を組み立てるには、こうした失われた情報を外部で保持しておき、組み立て時に補ってやるか、そもそもオリジナルと同一のXML文書を再現する必要のない利用形態を想定する必要がある。
  • 完全にオリジナルなXML文書を復元するためには、相当量のデータのストアが必要になる。

とはいえ、定型のXML文書を扱うだけならば、実績、信頼性、データベースとしての機能の優位性という面で、リレーショナルデータベースを選択するメリットは大きいといえます。

ネイティブXMLデータベース

native.gif

 XMLの階層構造や、名前空間等の付加情報が複雑な場合には、ネイティブXMLデータベースを選択すべきかもしれません。

 ネイティブXMLデータベースは、XML文書をそのまま、もしくは最小限の変換を行って保存し、完全にオリジナルなものと同じ状態で取り出せます。また、保存したXML文書に対してインデックス付けをしたり、DOMツリーに変換するなど、XML文書内の目的のデータを素早く検索、更新できるような工夫がされています。

 この方式の特徴は、XML文書の特徴を生かした管理ができることにあります。

  • XML文書が持つタグの情報を元にした検索や追加、削除、更新などが可能。
  • 検索結果としてXML文書の任意の一部だけを取得できたり、XML文書の一部の更新や追加が可能といった機能を提供
  • 自由に任意の構造を持つXML文書を保存し、データベースに追加することができる。

 しかし、デメリットもあります。

  • 業界標準的な問い合わせ言語が確立されていない。
  • XMLデータベース製品ごとに問い合わせの方法が異なり、互換性もほとんどない
  • 大手のRDBMSに比べて、データベースとしての機能、実績、信頼性の面で見劣りすることが多い。

 これらの特徴を踏まえた上で、実際にネイティブXMLデータベースの機能と動作を見ていきましょう。

Xindice

 Xindice(イタリア語風で『ジンディーチェ(zeen-dee-chay)』と呼ぶらしい)は、The Apache XML Projectの1つとして開発されているオープンソースのネイティブXMLデータベースです。Javaで実装されています。固有の特徴としては、

  1. Well-Formedドキュメントを格納可能

     スキーマを定義することなくXML文書を格納することができます。しかし、現在のバージョンではスキーマによる検証には対応していません。
  2. 問い合わせ言語はXPath

     問い合わせには、現在ネイティブXMLデータベースで標準的に用いられているXPathを用います。XQueryはまだWorking Draftということもあって、まだサポートされていません。
  3. 更新言語はXML:DB XUpdate

     XMLデータベースの更新に関する標準的な仕様はないのですが、XML:DB Initiativeという団体が策定しているXML:DB XUpdateという仕様に対応しています。
  4. エレメントや属性にインデックスを付けることが可能

     問い合わせを高速化できるようインデックスを付けることが可能です。
  5. セキュリティ関連の機能はない

     現在のバージョンでは、ユーザー認証等の機能がありません。リモートからのアクセスを制限するにはネットワークレベルでフィルタリングする等の工夫が必要になります。
  6. すべてJavaで記述されている

     J2SEが動作する環境であれば、動作可能です。ただし、特定のプラットフォームに特化した機能や厳格なトランザクション性の保証といったことはサポートされません。
  7. Java用のAPIが用意されている

     Javaプログラムからアクセスするための、Javaのクラスライブラリが用意されています。また、CORBAやXML-RPCベースのインターフェイスも用意されています。

ということがあります。まずは、簡単に使って見ましょう。

インストール

 それでは、Windows上でXindiceのインストールをします。 XindiceはJava環境下で動作します。まず、JDK1.3をインストールしてください。JDK1.4を使う際には、JDKに付属するXercesとXalanを無効にする必要があります。JDK1.2以前では動作しません。そして、環境変数の設定をしてください。Xindiceの起動スクリプト内に記述してもかまいませんが、OSの設定で変更しまった方がクライアントにも適応されるので簡単です。JDKをC:\jdk1.3.1_06にインストールしたとすると、

JDKの環境設定(JAVA_HOME, PATH)

> JAVA_HOME=C:\jdk1.3.1_06
> PATH=%PATH%;%JAVA_HOME%\bin

 XindiceのWebページから実行ファイル(xml-xindice-1.0.zip)をダウンロードします。現在のバージョンは1.0です。ダウンロードしたファイルを解凍すると、xml-xindice-1.0 というフォルダができます。次に、環境変数の設定をします。C:\xml-xindice-1.0 に解凍したとすると、

Xindiceの環境設定(XINDICE_HOME, PATH)

> XINDICE_HOME=C:\xml-xindice-1.0
> PATH=%PATH%;%XINDICE_HOME%\bin

 以上で、インストールは完了です。

起動と停止

Xindiceの起動

> %XINDICE_HOME%startup.bat 

 正常に起動した際には、次のようなメッセージが出力されます。db はデフォルトで作成されるデータベースインスタンスです。

C:\xml-xindice-1.0>startup.bat
java -classpath … -noverify org.apache.xindice.core.server.Xindice C:\xml- xindice-1.0\config\system.xml

Xindice 1.0 (Birthday)

Database: 'db' initializing
Script: 'GET' added to script storage
Service: 'db' started
Service: 'HTTPServer' started @ http://hostname:4080/
Service: 'APIService' started

Server Running

Xindiceの終了

> xindiceadmin shutdown -c /db 

コマンドラインからの操作

 まずはコマンドラインからデータベースを操作してみましょう。登録データとして次の2つのXMLファイルを準備します。

filesample01.xml

<?xml version="1.0" encoding="Shift_JIS" ?>
<diary year="2002" month="09">
 <date day="08">
  <item>一日中、部屋と押入れの整頓に費やす。
9月中に&lt;a href="http://www.bidders.co.jp/bpu/1310605">オークション&lt;/a>かなんかで、要らないものが処分できたらいいなぁ…。
MDプレイヤーとか、もう要らないよなぁ…。</item>
 </date>
</diary>

filesample02.xml

<?xml version="1.0" encoding="Shift_JIS" ?>
<diary year="2002" month="08">
 <date day="14">
  <item>今日は朝から、帰省移動。
4時間ぐらいかかるんですよ、電車で…。
途中、乗り換えもあって、ずっと寝ていられないし…。</item>
 </date>
 <date day="15">
  <item>今日は、有給充当日。
「自主的に有給とってね」の日です。
なんか、納得いかないんですけど… (^^;;</item>
  <item>&lt;a href="http://www.town.fukumitsu.toyama.jp/">私の田舎のWebサイト&lt;/a>も、結構こってました(++)
イベント・施設情報が検索できたり、町民の意見交換用の掲示板とか…。
なかなかよろしいんじゃないでしょうか?
&lt;a href="http://www.e-fuku3.com/">ユビキタウンふくみつ&lt;/a>なんてポータルサイトもあったりして…。
でも、Java使ってるらしく、ページ遷移が遅いなぁ、と思うのは私だけ?</item>
 </date>
</diary>

 Xindiceでは、XML文書を「コレクション」と「ドキュメント」という単位で管理します。ちょうど、ファイルシステムで言うところの「ディレクトリ」と「ファイル」の関係に相当します。同じような階層構造を成します。

 まず、db に新規のコレクションを作成します。名前をsampledbとします。

コレクションの追加

> xindiceadmin ac -c /db -n sampledb
Created : /db/sampledb 

 このコレクションに、先ほどの2つのXML文書を、それぞれsam01、sam02というキーに関連付けて、追加します。

ドキュメントの追加

> xindiceadmin ad -c /db/sampledb -f sample01.xml -n sam01
Added document /db/sampledb/sam01
> xindiceadmin ad -c /db/sampledb -f sample02.xml -n sam02
Added document /db/sampledb/sam02

 では、これらのドキュメントを検索してみましょう。検索条件はXPathで指定します。

検索

> xindiceadmin xpath -c /db/sampledb -q "//item"
<?xml version="1.0"?>
<item xmlns:src="http://xml.apache.org/xindice/Query" src:col="/db/sampledb" src:key="sam01">
一日中、部屋と押入れの整頓に費やす。
9月中に<a href="http://www.bidders.co.jp/bpu/1310605">オークション</a>かなんかで、要らないものが処分できたらいいなぁ…。
MDプレイヤーとか、もう要らないよなぁ…。</item>
<?xml version="1.0"?>
<item xmlns:src="http://xml.apache.org/xindice/Query" src:col="/db/sampledb" src:key="sam02">今日は朝から、帰省移動。
4時間ぐらいかかるんですよ、電車で…。
途中、乗り換えもあって、ずっと寝ていられないし…。</item>
<?xml version="1.0"?>
<item xmlns:src="http://xml.apache.org/xindice/Query" src:col="/db/sampledb" src:key="sam02">今日は、有給充当日。
「自主的に有給とってね」の日です。
なんか、納得いかないんですけど… (^^;;</item>
<?xml version="1.0"?>
<item xmlns:src="http://xml.apache.org/xindice/Query" src:col="/db/sampledb" src:key="sam02">
<a href="http://www.town.fukumitsu.toyama.jp/">私の田舎のWebサイト</a>も、結構こってました(++)
イベント・施設情報が検索できたり、町民の意見交換用の掲示板とか…。
なかなかよろしいんじゃないでしょうか?
<a href="http://www.e-fuku3.com/">ユビキタウンふくみつ</a>なんてポータルサイトもあったりして…。
でも、Java使ってるらしく、ページ遷移が遅いなぁ、と思うのは私だけ?</item>

 検索結果が1件ずつ、XMLドキュメントの形で戻ってきます。

[補足] 私の環境では、"//date[@day='08']" のような書式のXPath指定をコマンドラインで実行すると、『使い方が誤っています。』と言われます。コマンドプロンプトの問題でしょうか。アプリケーションからの検索では、この書式も問題ありません。

 その他の主なコマンドは下記の通りです。

コレクションのリスト表示

xindice lc -c {ベースとなるコレクション} 

コレクションの削除

xindiceadmin dc -c {ベースとなるコレクション} -n {削除するコレクションの名前} 

ドキュメントのリスト表示

xindice ld -c {ベースとなるコレクション} 

ドキュメントの取得

xindice rd -c {ベースとなるコレクション} -n {ドキュメントのキー} -f {書き出すファイル名} 

ドキュメントの削除

xindice dd -c {ベースとなるコレクション} -n {ドキュメントのキー} 

 ちなみに、DBからドキュメントを取得するとわかるのですが、ドキュメントの文字コードは全てUTF-8に変換されています。

Javaプログラムからの利用(検索処理)

 Xindiceの検索用APIは、JDBCドライバのような形で提供されています。DBクライアントプログラムの実行には、%XINDICE_HOME%java\lib 以下の、xmldb.jar、xindice.jar、openorb-1.2.0.jar、xalan-2.0.1.jar、xerces-1.4.3.jar をJavaのクラスパスに追加する必要があります。

 XPath検索に使うプログラムのサンプルを下記に示し、解説していきます。

検索サンプルプログラム

01.  try {
02.     String driver = "org.apache.xindice.client.xmldb.DatabaseImpl";
03.     Class c = Class.forName(driver);
04.
05.     org.xmldb.api.base.Database database = 
06.         (org.xmldb.api.base.Database)c.newInstance();
07.     org.xmldb.api.DatabaseManager.registerDatabase(database);
08.
09.     org.xmldb.api.base.Collection col = 
10.         org.xmldb.api.DatabaseManager.getCollection
11.             ("xmldb:xindice://localhost:4080/db/sampledb");
12.
13.     org.xmldb.api.modules.XPathQueryService service = 
14.         (org.xmldb.api.modules.XPathQueryService)
15.             col.getService("XPathQueryService", "1.0");
16.
17.     String xpath = "//date[@day='14']/item";
18.     org.xmldb.api.base.ResourceSet resultSet = service.query(xpath);
19.
20.     // イテレータの取得
21.     org.xmldb.api.base.ResourceIterator results = resultSet.getIterator();
22.     // リソースがある間、繰り返す
23.     while (results.hasMoreResources()) {
24.         // 次のリソースを取得
25.         org.xmldb.api.base.Resource res = results.nextResource();
26.         // ResourceがXMLResourceかどうかの確認
27.         if (res.getResourceType().equals("XMLResource")) {
28.             // 内容を文字列として表示
29.             System.out.println((String)res.getContent());
30.             // DOMとして内容を取得
31.             org.xmldb.api.modules.XMLResource xmlres = 
32.                 (org.xmldb.api.modules.XMLResource)res;
33.             org.w3c.dom.Document document =
34.                 (org.w3c.dom.Document)xmlres.getContentAsDOM();
35.             // documentに対する処理を行う
36.             document = (org.w3c.dom.Document)clean(document);
37.             NodeList nl = document.getFirstChild().getChildNodes();
38.             for (int i = 0; i < nl.getLength(); i++) {
39.                 System.out.println("item[" + i + "] = "
40.                     + nl.item(i).getFirstChild().getNodeValue());
41.             }
42.         } else {
43.             System.out.println("not XMLResource");
44.         }
45.     }
46. } catch (XMLDBException ex) {
47.     ex.printStackTrace();
48. } catch (InstantiationException ex) {
49.     ex.printStackTrace();
50. } catch (IllegalAccessException ex) {
51.     ex.printStackTrace();
52. } catch (ClassNotFoundException ex) {
53.     ex.printStackTrace();
54. } finally {
55.     if (col != null) {
56.         col.close();
57.     }
58. }

02~07行目:

 Databaseインターフェイスの実装クラスを、DatabaseManagerクラスに登録します。これは、JDBCのDriverインターフェイスの実装クラスとDriverManagerクラスとの関係に似ています。XML:DBで決められた仕様に従ったドライバをXindiceが提供しているので、こういった手順になっています。

09~11行目:

 コレクションを取得します。ここで使うURIは、"xmldb:xindice://{ホスト名}:{ポート番号}/{データベースインスタンス名}/{コレクション}" となります。ポート番号を省略すると4080となり、更にホスト名を省略するとローカルのDBインスタンスを探索します。

13~15行目:

 コレクションに対して行いたいことに応じて、対応するサービスを取得します。今回は、XPathによる検索を行うのでXPathQueryServiceを取得します。

17~18行目:

 サービスのquery()メソッドにXPathクエリを渡して、ResourceSet(JDBCでいうところのResultSet)を取得します。

20~21行目:

 ResourceSetには検索結果の集合が格納されています。1つ1つのResourceを取り出すときに、ResourceIteratorを使うと簡単です。

22~45行目:

 Resourceには種類があり、現在のorg.xml.api.moduleパッケージには、XMLResourceとBinaryResourceが定義されています。将来的には別のResourceも定義されるかもしれません。ここではResourceTypeを判別し、XML文書に限って扱うこととしています。33行目で、getContentAsDOMメソッドでDocumentを得ています。
 なお、36行目で使っているcleanメソッドは、空のテキストノードを削除するために自作したものです。

   public static Node clean(Node node) {
       if (node.hasChildNodes()) {
           Node childnode = node.getFirstChild();
           while (childnode != null) {
               if (childnode.getNodeType() == Node.TEXT_NODE
                   && childnode.getNodeValue().trim().length() == 0) {
                   Node delnode = childnode;
                   childnode = childnode.getNextSibling();
                   node.removeChild(delnode);
               } else {
                   if (childnode.hasChildNodes()) {
                       clean(childnode);
                   }
                   childnode = childnode.getNextSibling();
               }
           }
       }
       return node;
   }

56行目:

 処理が終了したら、コレクションをクローズして解放します。

 以上で、基本的な参照方法の説明は終わりですが、%XINDICE_HOME%\java\example には、非常に興味深いサンプルがありますので、是非ご参照下さい。

XUpdate

 次に、XMLデータの追加・更新・削除を行います。Xindiceでは、データの追加・更新・削除を行うためにXUpdateという言語を採用しています。XUpdateは、XML:DB Initiativeが開発したXML-DB用の言語仕様で、XUpdateのページワーキングドラフト仕様が公開されています。

 ちなみに、XUpdateのページには、XUpdateのリファレンス実装として「Lexus 0.2.2」というXML-DBが公開されています。

XMLデータの追加

 XMLデータの追加プログラムのサンプルを示し、解説していきます

 例えば、day属性が「08」であるdateノードの子ノードに、itemノードを追加するとします。

XMLデータ追加サンプルプログラム

01.  try {
02.        String driver = "org.apache.xindice.client.xmldb.DatabaseImpl";
03.        Class c = Class.forName(driver);
04.
05.        org.xmldb.api.base.Database database = 
06.            (org.xmldb.api.base.Database)c.newInstance();
07.        org.xmldb.api.DatabaseManager.registerDatabase(database);
08.
09.        col = org.xmldb.api.DatabaseManager.getCollection
10.            ("xmldb:xindice://localhost:4080/db/sampledb");
11.
12.        // XUpdateQueryServiceの取得
13.        org.xmldb.api.modules.XUpdateQueryService service =
14.            (org.xmldb.api.modules.XUpdateQueryService)
15.                col.getService("XUpdateQueryService", "1.0");
16.
17.        String xupdate;
18.        xupdate = "<xupdate:modifications version=\"1.0\" "
19.                + "xmlns:xupdate=\"http://www.xmldb.org/xupdate\">"
20.                + "<xupdate:append "
21.                + "select=\"/diary/date[@day='08']\" child=\"last()\">"
22.                + "<item sam=\"test\">sample</item>"
23.                + "</xupdate:append>"
24.                + "</xupdate:modifications>";
25.        long num = service.update(xupdate);
26.    } catch (InstantiationException ex) {
27.        ex.printStackTrace();
28.    } catch (IllegalAccessException ex) {
29.        ex.printStackTrace();
30.    } catch (ClassNotFoundException ex) {
31.        ex.printStackTrace();
32.    } catch (XMLDBException ex) {
33.        ex.printStackTrace();
34.    } finally {
35.        if (col != null) {
36.            col.close();
37.        }
38.    }

02~10行目:

 参照の際と同じです。Databaseの実装クラスを登録し、コレクションを取得しています。

12~15行目:

 XUpdateQueryServiceを取得します。

17~23行目:

 XUpdate言語の構文です。xupdate:appendノードのselect属性で指定したノードに、xupdate:appendの子ノードを追加します。

 child属性で追加する場所を指定しています。指定が無い場合は「last()」になります。

24行目:

 サービスのupdate()メソッドにXUpdate構文を渡しています。戻りのlong値は「更新されたノードの数」です。

 実は、追加・挿入するXMLデータの要素、属性に日本語テキストがあると、

org.omg.CORBA.DATA_CONVERSION: minor code: 1398079494 completed: No

というエラーが表示されて追加できません。いろいろ文字コード変換してみましたが、うまく動かないようです。Xindice1.0のXUpdate実装は、日本語未サポートということのようですね。

 XMLデータの追加の方法は、他にもあります。以下に紹介します。

XMLデータの追加(2)

 下記の xupdate:insert-after を使ったXUpdate構文でも、上記 xupdate:append と同じ動作となります。

xupdate:insert-after

<xupdate:modifications version="1.0" xmlns:xupdate="http://www.xmldb.org/xupdate">
 <xupdate:insert-after select="/diary/date[@day='08']/item[last()]">
  <item sam="test">sample</item>
 </xupdate:insert-after>
</xupdate:modifications>

XMLデータの追加(3)

 xupdate:insert-before の動作も、名前から推して知るべしです。

xupdate:insert-before

<xupdate:modifications version="1.0" xmlns:xupdate="http://www.xmldb.org/xupdate">
 <xupdate:insert-before select="/diary/date[@day='08']/item[position()='1']">
  <item sam="sample">test</item>
 </xupdate:insert-before>
</xupdate:modifications>

XMLデータの更新

 xupdate:update は、文字列の更新をします。「ノードセットの置き換え」はできません。その際には、後で説明するxupdate:removeとxupdate:insert-afterを使って、「削除して追加する」必要があります。

xupdate:update

<xupdate:modifications version="1.0" xmlns:xupdate="http://www.xmldb.org/xupdate">
 <xupdate:update select="/diary/date[@day='08']/item[last()]/@sam">
  sample1
 </xupdate:update>
</xupdate:modifications>

XMLデータの削除

 xupdate:remove は、ノードの削除をします。

xupdate:remove

<xupdate:modifications version="1.0" xmlns:xupdate="http://www.xmldb.org/xupdate">
 <xupdate:update select="/diary/date[@day='08']/item[last()]" />
</xupdate:modifications>

その他のインストラクション

 xupdate:rename は、ノード名を変更します。
 xupdate:variable は、変数を定義できます。
 xupdate:value-of は、他のノードを参照できます。
 xupdate:if は、条件の指定ができます。

 これらは、XUpdate仕様に記述されていますが、Xindice1.0の実装は不完全なようです。

インデックスの指定

 リレーショナルデータベースでも必ずといっていいほど利用されるインデックスの管理についてです。管理コマンドを説明します。

インデックスのリスト表示

xindiceadmin li -c {コレクション} 

インデックスの追加

xindiceadmin ai -c {コレクション} -n {インデックス名} -p {パターン} 

 パターンはXPath指定です。
インデックスの削除

xindiceadmin di -c {コレクション} -n {インデックス名} 

 インデックスをつけると、ドキュメント数の増加に伴う参照時間の増加を抑えられます。ほぼ、リレーショナルデータベースと同じ感覚です。

 Xindice1.0の実装では、内容に日本語が入るものに対してインデックスをつけると、うまく動作しないようです。

Xindiceについての補足

プログラムからColectionの管理を行うには、CollelctionManager を使う。

おわりに

 今回の調査で目に付いたのは、Xindiceの日本語対応の不備です。まだまだ日本語圏で使えるレベルではないようですね。XMLといえばUCSから生まれたUTF-8を標準としていることからもわかるように、国際化を主眼の一つとしているはずです。XML-DBには、ぜひともmulti-language対応となって欲しいものです。

 そういう点では、株式会社メディアフュージョンが販売しているYggdrasill(イグドラシル)というXML-DBは、2バイト文字対応、RELAXによるXMLデータのバリデーション機能等、日本語圏で利用するには最適であると思われます。

 今後も各方面でのXMLの普及に伴い、XMLデータをスムーズに利用、ストアする必要性は間違いなく高まるでしょう。そのシステムに合った設計を見極め、XML-DBをうまく使えるようなエンジニアが求められるようになるかもしれません。そういう人材が希少な今がチャンスかもしれませんね。


添付ファイル: filesample02.xml 2656件 [詳細] filesample01.xml 2807件 [詳細] filerelational.gif 2518件 [詳細] filenative.gif 3384件 [詳細]

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS