はじめに
XMLが普及している昨今、多くの状況でXMLファイルを編集する機会が増えています。例えば、システムで流通するデータを保存する形式であったり、各種ツールの設定ファイルであったりします。こんなとき、それぞれのXMLファイルに適したエディタが欲しくなるのではないか、と思います。ですが、DOM、SAXやXMLバインディングを利用してもエディタまで自動的に作成してくれるわけではありません。エディタを作成するためには、XMLの基本技術から、GUIの技術まで広範囲に渡る技術の習得が必要になります。
XMLファイルからエディタが自動生成できればなぁ。。。
EMF(Eclipse Modeling Framework)はそんな要望を「半分だけ」叶えてくれます。
対象読者
Eclipseを利用してJavaプログラムを記述したことのある方、かつ、XMLに関する理解をお持ちの方を対象とします。
必要な環境
サンプルは以下の環境で動作確認を行っています。
- Eclipse SDK 3.1RC2
- EMF ALL 2.1.0 I200506160200
- Trang Version 20030619(XML Schema生成のため)
サンプル概要
手元にはXMLファイルのみ
今回の例では、最初に持っているものはXMLファイルのみという前提にします。ここから
- XMLファイルからXML Schemaを自動生成
- XMLスキーマからEclipseプラグインを生成
- Eclipseプラグインをカスタマイズしてエディタを作成
という流れになります。
サンプルXMLファイル
当記事で利用するサンプルXMLファイルは以下のような単純なXMLファイルとします。
<?xml version="1.0"?> <personnel> <person id="1"> <name>Takahiro Shida</name> <job>Programmer</job> </person> <person id="2"> <name>Tarou Syouei</name> <job>Editor</job> </person> </personnel>
では、このファイルを元にエディタを作成していきましょう。
XMLからXML Schemaを自動生成
Trangというツールを利用して、XMLファイルからXML Schemaを自動生成することが出来ます。Trangは元々Relax NGというXML文章定義言語用のスキーマ変換ツールとして作成されました。数年前に開発自体は終了していますが、今回使用するバージョンでは、XMLファイルから直接XML Schemaを生成することが出来ます。
個人情報XMLが個人情報スキーマ定義に
Trangを解凍したディレクトリと同じディレクトリに「personnel.xml」を配置し、コマンドプロンプト上で次のようなコマンドを実行します。
java -jar trang.jar personnel.xml personnel.xsd
Trangは拡張子からどのようなスキーマ変換をするのかを自動的に判断します。今回は「.xml」を入力とし、「.xsd」を出力としましたので、TrangはXML形式からXML Schema形式への変換を試みます。
生成した個人情報スキーマ定義
生成した個人情報スキーマは以下のようになります。今回はこのスキーマファイルの中身を深く知る必要はありません。
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> <xs:element name="personnel"> <xs:complexType> <xs:sequence> <xs:element maxOccurs="unbounded" ref="person"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="person"> <xs:complexType> <xs:sequence> <xs:element ref="name"/> <xs:element ref="job"/> </xs:sequence> <xs:attribute name="id" use="required" type="xs:integer"/> </xs:complexType> </xs:element> <xs:element name="name" type="xs:string"/> <xs:element name="job" type="xs:NCName"/> </xs:schema>
Eclipseプラグインの自動生成
次に、XML SchemaからXMLを編集するEclipseプラグインを作成します。ここで、EMF(Eclipse Modeling Framework)を利用します。Modeling Frameworkと言われると、とても難しそうに思うかもしれません。実際、コード生成の部分や、メタモデリングの概念は難しいと思います(投稿者もよく理解していません)。ですので、EMFをそのような難しい概念で捕らえるのではなく、単なるコード生成ツールだと考えればとっつきやすくなるのではないかと思います。
Eclipseプラグイン作成までの流れは以下のようになります。
- EMFプロジェクトを作成
- XML Schemaを読み込んで、EcoreモデルとGenモデルを作る
- Genモデルから、EMFエディタを作る
EMFプロジェクト新規作成
Eclipseを起動し、[File]メニューから[New]を選択し、[Other]をクリックします。
すると、ウィザードが起動しますので、ツリーの中から[Eclipse Modeling Framework][EMF Project]を選択します。
プロジェクト名は適当に入力してください。今回の例では「personnel」としておきます。ウィザードを進めると、モデルインポーターを選択しろ、と言われるはずです。ここで、[XML Schema]を選択し、先ほど作成したXML Schemaファイルのパスを入力します。このとき、何もエラーが表示されなかったら、読み込みは成功です。そのまま[Next] を選択し、テーブルに表示されている「personnel」にチェックを入れてウィザードを終了してください。
コード自動生成
EMFプロジェクトを作成すると、自動的に「personnel.genmodel」と「Personnel.ecore」が作成されます。そして、「personnel.genmodel」をエディタで開いている状態になっています。ここからどうやってコードを自動生成するのか。
答えは簡単です。表示されているツリー要素のルートを選択し
[右クリック] →[Generate All] を選ぶだけです。
すると、自動的に「personnel」プロジェクトにJavaソースやパッケージが追加され、自動的に「personnel.edit」、「personnel.editor」プロジェクトが生成されます。
実は、これだけですでにXMLファイルを編集するエディタは完成しているのです。
動作確認
Eclipse上の[Run As] メニューから[Eclipse Application] を選択してください。新しくEclipseが起動します。
新しい個人情報XMLを作る
新しく起動したEclipse上で[File]メニューの[New][Other]を選択し、ウィザードを開いてください。ウィザード中の[Example EMF Model Creation Wizard]中の[Personnel Model]を選択します。生成するファイル名は適当で結構です(今回はウィザードに言われるまま「My.personnel」としました)。
ウィザード中で作成するモデルは何かと聞かれるので、コンボボックスから[Personnel]を選択します。ファイル生成が完了すると、以下のようなエディタが開きます。
これは、ツリー構造でモデルを編集するエディタになります。[右クリック]→[New Child]で子要素を作っていくことが出来ます。適当に子要素を追加し、ファイルの保存を行いましょう。
ここで、保存した「My.personnel」の中身はどうなっているでしょうか? テキストエディタで中身を見てみることにします。エディタ上ではツリー構造として扱っていたものは、実は個人情報XMLファイルそのものだということがわかります。
<?xml version="1.0" encoding="UTF-8"?> <personnel> <person id="1"> <name>hoge</name> <job>hoge</job> </person> <person id="2"> <name>foo</name> <job>foo</job> </person> <person id="3"> <name>var</name> <job>var</job> </person> <person id="4"> <name>hogefoo</name> <job>hogefoo</job> </person> <person id="5"> <name>hogefoovar</name> <job>hogefoovar</job> </person> </personnel>
既存のXMLファイルを読み込む
サンプルとして最初に作成した、個人情報XMLファイル「personnel.xml」をコピーして、拡張子を「personnel」に変更します。変更後に、そのままダブルクリックをしてエディタから開いてみましょう。正しくツリー構造が構築されていると思います。
自動生成によって作成されるエディタ
エディタはマルチページエディタとなっていて、複数の編集方法を提供しています。
- Selection : 選択エディタ。ツリーとして複数のファイルを同時に編集できます。
- Parent : 子から親への逆ツリーを表示するエディタです。
- List : 選択したノードの子要素一覧を表示するエディタです。
- Tree : 子要素をツリーとして表示するエディタです。
- Table : 子要素をテーブルとして表示するエディタです。
- Tree With Column : 子要素をテーブルとツリーで表示するエディタです。
エディタの改善
自動生成されたエディタは、ツリー構造として個人情報XMLファイルを編集するものでした。これだと、本当に表示するべきID、名前、職業といった要素がプロパティシートに表示されるのみとなってしまい、使い勝手が良くありません。せめてテーブルとして表示するように、エディタを改善していきます。
編集するポイント
エディタを改善するにはどうしたらよいのでしょう。それは、[Generate All]を行い自動生成したコードを編集する必要があります。
生成した「personnel.editor」プロジェクトの「Personnel.presentation.PersonnelEditor.java」を開いてください。この中の、createPages()
メソッドが鍵になります。ここでは、それぞれのエディタを作成しています。今回は、最後に作成されているTable Tree Viewerを修正することとします。
表示するカラムを増やす
Table Tree Viewer作成部分を見てみましょう(注釈は投稿者が追加した部分もあります)。
// This is the page for the table tree viewer. // { ViewerPane viewerPane = new ViewerPane(getSite().getPage(), PersonnelEditor.this) { public Viewer createViewer(Composite composite) { return new TreeViewer(composite); } public void requestActivation() { super.requestActivation(); setCurrentViewerPane(this); } }; viewerPane.createControl(getContainer()); treeViewerWithColumns = (TreeViewer)viewerPane.getViewer(); /* * Tree Table Viewerを作成しています。 * TreeクラスとなっているのはEclipse 3.1の変更によるものです。 */ Tree tree = treeViewerWithColumns.getTree(); tree.setLayoutData(new FillLayout()); tree.setHeaderVisible(true); tree.setLinesVisible(true); /* * 表示するカラムの情報を設定しています。 */ TreeColumn objectColumn = new TreeColumn(tree, SWT.NONE); objectColumn.setText(getString("_UI_ObjectColumn_label")); objectColumn.setResizable(true); objectColumn.setWidth(250); TreeColumn selfColumn = new TreeColumn(tree, SWT.NONE); selfColumn.setText(getString("_UI_SelfColumn_label")); selfColumn.setResizable(true); selfColumn.setWidth(250); /* * Tree Tableに設定する情報です。 * 上から、 * カラムを編集するときのプロパティ * (このコードでは利用されません) * カラムに設定する値を生成するプロバイダー * カラムとして表示する値を生成するプロバイダー * となっています。 */ treeViewerWithColumns.setColumnProperties( new String [] {"a", "b"}); treeViewerWithColumns.setContentProvider( new AdapterFactoryContentProvider(adapterFactory)); treeViewerWithColumns.setLabelProvider( new AdapterFactoryLabelProvider(adapterFactory)); createContextMenuFor(treeViewerWithColumns); int pageIndex = addPage(viewerPane.getControl()); /* * ページを追加し、そのタイトルをTree With Columnとしています。 */ setPageText(pageIndex, getString("_UI_TreeWithColumnsPage_label"));
まずは、テーブルとして表示するカラムを増やしていきます。今回はID、名前、職業を表示しようと思いますので、その通りにTreeColumn
を作成していきます。
TreeColumn objectColumn = new TreeColumn(tree, SWT.NONE); objectColumn.setText(getString("_UI_ObjectColumn_label")); objectColumn.setResizable(true); objectColumn.setWidth(250); /* * TODO selfColumnは使わない. */ //TreeColumn selfColumn = new TreeColumn(tree, SWT.NONE); //selfColumn.setText(getString("_UI_SelfColumn_label")); //selfColumn.setResizable(true); //selfColumn.setWidth(250); /* * TODO IDを表示するカラムを追加. */ TreeColumn idColumn = new TreeColumn(tree, SWT.NONE); idColumn.setText(getString("_UI_idColumn_label")); idColumn.setResizable(true); idColumn.setWidth(100); /* * TODO 名前を表示するカラムを追加. */ TreeColumn nameColumn = new TreeColumn(tree, SWT.NONE); nameColumn.setText(getString("_UI_nameColumn_label")); nameColumn.setResizable(true); nameColumn.setWidth(100); /* * TODO 職業を表示するカラムを追加. */ TreeColumn jobColumn = new TreeColumn(tree, SWT.NONE); jobColumn.setText(getString("_UI_jobColumn_label")); jobColumn.setResizable(true); jobColumn.setWidth(250);
ここで、カラムの表示するテキストを取得するために、getString("_UI_xxxColumn_label")
として文字列を取ってきています。この情報は「personnel.editor」プロジェクトにある「plugin.properties」に記述されています。今回は、ID、名前、職業用のキーを新しく作りましたので、「plugin.properties」にも追記しておきます。
#id, name, job _UI_idColumn_label = ID _UI_nameColumn_label = NAME _UI_jobColumn_label = JOB
カラムに表示するラベルを生成する
次に、「personnel.edit」プロジェクトを見てみます。このクラスがモデルをどのように表示するかを決めています。まずは、PersonnelItemProviderAdapterFactory
を選択し、コンストラクターを開きます。その後に、supportedTypes.add(ITableItemLabelProvider.class)
の一行を追加します。これは、Personnelモデルがテーブルとしても表示できるという意味の設定になります。
/** * This constructs an instance. * <!-- begin-user-doc --> * <!-- end-user-doc --> * @generated */ public PersonnelItemProviderAdapterFactory() { supportedTypes.add(IEditingDomainItemProvider.class); supportedTypes.add(IStructuredItemContentProvider.class); supportedTypes.add(ITreeItemContentProvider.class); supportedTypes.add(IItemLabelProvider.class); supportedTypes.add(IItemPropertySource.class); /* * TODO テーブルとして表示できるようにする. */ supportedTypes.add(ITableItemLabelProvider.class); }
次に、実際にラベルとして返す値を設定します。PersonTypeItemProvider
を開き、implements
にITableItemLabelProvider
を追加します。すると実装するメソッドとしてgetColumnText(Object object, int columnIndex)
とgetColumnImage(Object object, int columnIndex)
が追加されます。この中をカラム番号によって返却する文字列を変えるように修正します。
public String getColumnText(Object object, int columnIndex) { switch (columnIndex) { case 1: /* * TODO 番号が1の場合はIDを返す. */ return ((PersonType) object).getId().toString(); case 2: /* * TODO 番号が2の場合は名前を返す. */ return ((PersonType) object).getName(); case 3: /* * TODO 番号が3の場合は職業を返す. */ return ((PersonType) object).getJob(); } return getText(object); } public Object getColumnImage(Object object, int columnIndex) { /* * TODO ColumTextがnullでない場合はイメージを返す. */ return getColumnText(object, columnIndex) != null ? getImage(object) : null; }
これで、作成したエディタがPersonType
の内容をテーブルとして一覧で表示するようになります。アウトラインビュー上で<personnel> Type
を選択して見てください。
最後に
EMFはやはり難しいイメージが付きまとっていると思います。自動生成されたコードをカスタマイズすることをEMFプロジェクトは推奨していますが、実際にどこをどうカスタマイズすればいいかの情報が不足しています。ですが、EMFの一部を利用することで多くの部分が自動化されます。 EclipseのEMFモデルを使用したGEFアプリケーションの作成でおっしゃられているように、EMFは他のフレームワークと連携することによってその弱点を補うことが出来るでしょう。