CodeZine(コードジン)

特集ページ一覧

EclipseとEMFを使用した簡易XMLエディタの作成

XMLスキーマにもとづくXMLエディタの作成とカスタマイズ

  • LINEで送る
  • このエントリーをはてなブックマークに追加

EMF(Eclipse Modeling Framework)やTrang等のオープンソースソフトウェアを使用して、XMLファイルから、XMLを編集するEclipseプラグインを自動的に生成する方法と、生成したプラグインをカスタマイズする方法を紹介します。

完成画像
完成画像

はじめに

 XMLが普及している昨今、多くの状況でXMLファイルを編集する機会が増えています。例えば、システムで流通するデータを保存する形式であったり、各種ツールの設定ファイルであったりします。こんなとき、それぞれのXMLファイルに適したエディタが欲しくなるのではないか、と思います。ですが、DOM、SAXやXMLバインディングを利用してもエディタまで自動的に作成してくれるわけではありません。エディタを作成するためには、XMLの基本技術から、GUIの技術まで広範囲に渡る技術の習得が必要になります。

 XMLファイルからエディタが自動生成できればなぁ。。。

 EMF(Eclipse Modeling Framework)はそんな要望を「半分だけ」叶えてくれます。

対象読者

 Eclipseを利用してJavaプログラムを記述したことのある方、かつ、XMLに関する理解をお持ちの方を対象とします。

必要な環境

 サンプルは以下の環境で動作確認を行っています。

補足
 Eclipse、EMFが最新でない場合も当記事の手法は有効です。しかし、JFaceのAPIやEMFが生成するコードが異なるため、サンプルのプロジェクトはそのまま動作いたしません。JDKは1.4以上のバージョンを利用してください。また、XML Schemaの知識があればTrangは必要ありません。
 

サンプル概要

手元にはXMLファイルのみ

 今回の例では、最初に持っているものはXMLファイルのみという前提にします。ここから

  1. XMLファイルからXML Schemaを自動生成
  2. XMLスキーマからEclipseプラグインを生成
  3. Eclipseプラグインをカスタマイズしてエディタを作成

 という流れになります。

サンプルXMLファイル

 当記事で利用するサンプルXMLファイルは以下のような単純なXMLファイルとします。

個人情報XMLファイル(personnel.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」を配置し、コマンドプロンプト上で次のようなコマンドを実行します。

Trangの実行
java -jar trang.jar personnel.xml personnel.xsd

 Trangは拡張子からどのようなスキーマ変換をするのかを自動的に判断します。今回は「.xml」を入力とし、「.xsd」を出力としましたので、TrangはXML形式からXML Schema形式への変換を試みます。

生成した個人情報スキーマ定義

 生成した個人情報スキーマは以下のようになります。今回はこのスキーマファイルの中身を深く知る必要はありません。

個人情報スキーマ定義(「personnel.xsd」)
<?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プラグイン作成までの流れは以下のようになります。

  1. EMFプロジェクトを作成
  2. XML Schemaを読み込んで、EcoreモデルとGenモデルを作る
  3. Genモデルから、EMFエディタを作る

EMFプロジェクト新規作成

 Eclipseを起動し、[File]メニューから[New]を選択し、[Other]をクリックします。

 すると、ウィザードが起動しますので、ツリーの中から[Eclipse Modeling Framework][EMF Project]を選択します。

新規作成ウィザード選択
新規作成ウィザード選択

 プロジェクト名は適当に入力してください。今回の例では「personnel」としておきます。ウィザードを進めると、モデルインポーターを選択しろ、と言われるはずです。ここで、[XML Schema]を選択し、先ほど作成したXML Schemaファイルのパスを入力します。このとき、何もエラーが表示されなかったら、読み込みは成功です。そのまま[Next] を選択し、テーブルに表示されている「personnel」にチェックを入れてウィザードを終了してください。

XML Schemaの読み込み
XML Schemaの読み込み
Ecoreモデルの読み込み
Ecoreモデルの読み込み

コード自動生成

 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ファイルそのものだということがわかります。

エディタで作ったファイル(My.personnel)
<?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作成部分を見てみましょう(注釈は投稿者が追加した部分もあります)。

修正前の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モデルがテーブルとしても表示できるという意味の設定になります。

サポートタイプの追加(PersonnelItemProviderAdapterFactory.java)
/**
 * 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を開き、implementsITableItemLabelProviderを追加します。すると実装するメソッドとしてgetColumnText(Object object, int columnIndex)getColumnImage(Object object, int columnIndex)が追加されます。この中をカラム番号によって返却する文字列を変えるように修正します。

ラベル文字列とラベルイメージの生成(PersonTypeItemProvider.java)
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は他のフレームワークと連携することによってその弱点を補うことが出来るでしょう。

参考資料

  • LINEで送る
  • このエントリーをはてなブックマークに追加

あなたにオススメ

著者プロフィール

  • NTTデータ先端技術株式会社 志田 隆弘(シダ タカヒロ)

    &lt;NTTデータ先端技術株式会社について&gt; データベース、ネットワーク、OS、ミドルウェアの基盤技術を武器にシステムの技術面でのコンサルテーションや最新製品の調査を行う専門家集団。 &lt;筆者について&gt; Ja-Jakartaプロジェクトの末席にこっそりと参加しています。...

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5