2010年5月20日木曜日

プラグインを作ってみよう:テキスト形式の入力

はい。第3回です。

今回は、前回作成したテキスト書き出しに対応して、読み出し処理を作成することにします。
前回の復習をしつつ、書き出しと同様にJavaScriptコードとJavaコードを記述していきましょう。


テキストの入力機能は、以下のようなユーザインタフェースで実現することにしましょう。
(前回のインタフェースと基本的な考え方は同じなので、手書きの図は省略します。)
  1. ファイルメニューもしくは右クリックメニューから[テキスト形式で入力]を選択
  2. オープンするファイルの選択ダイアログが開くのでここから読み込み対象ファイルを選択
  3. 読み込み処理を実行 ... ファイルメニューから実行された場合はドキュメント全体を置き換え、右クリックメニューから実行された場合はそのノード以下に追加

ということにしてみましょう。
なお、ここでは、簡単化のため、現在開いているファイルが保存されていない場合の「閉じてよろしいですか」の問い合わせなどは行わないこととします。また、ノードあたりのテキストには改行が含まれていないものとみなし、テキストファイルでの1行=1ノードとなっているものだと考えます。


では、プラグインを作成していきましょう。
  1. 前回と同様、メニューに対する項目の追加処理をJavaScriptで実装します。
    実装する内容は前回とほぼ同様です。一部、項目名と関数名が変わっています。
    BEITELは全プラグインのスクリプトを同じJavaScriptのコンテキストで実行しますので、関数名などはプラグイン間で重ならないように注意してください。
    いつも通り、文字コードはUTF-8で保存してください。
    window.popup.appendChild(window.popup.createSeparator());
    var comp = window.popup.createMenuItem("テキスト形式で入力", function(ev){
    myImportText(ev.currentTarget);
    });
    window.popup.appendChild(comp);

    window.menu.file.appendChild(window.menu.createSeparator());
    comp = window.menu.createMenuItem("テキスト形式で入力", function(ev){
    myImportText(null);
    });
    window.menu.file.appendChild(comp);

    // 入力用関数
    function myImportText(targetNode)
    {
    // テキストの入力処理
    }


  2. myImportText関数にファイルダイアログを開く処理を実装します。
    これも前回と同様です。ここでは、開くダイアログを開きたいので、javax.swing.JFileChooserを使用し、showOpenDialogメソッドを呼び出すことにします。
    var dlg = new javax.swing.JFileChooser();
    var result = dlg.showOpenDialog(window.swingFrame);
    if(result != javax.swing.JFileChooser.APPROVE_OPTION) {
    return;
    }
    var path = dlg.selectedFile.absolutePath;


  3. テキスト入力処理をJavaで実装します。

    1. クラスパスにBEITELのクラスライブラリを追加します。
      (BEITELのディレクトリ)/lib/beitel.jarをクラスパスに追加してください。

    2. Javaコードを記述します。
      前回作成したjp.carabiner.beitel.sample.TextFormatUtilsクラスに対して、テキスト読み込みメソッドTreeNode importText(Document, path)を実装することにします。
      package jp.carabiner.beitel.sample;

      import java.io.BufferedReader;
      import java.io.FileInputStream;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.io.PrintStream;

      import jp.carabiner.treeeditor.TreeNode;
      import jp.carabiner.treeeditor.js.JSDocument;
      import jp.carabiner.treeeditor.js.model.JSTreeNode;

      /**
      * テキスト形式を処理するユーティリティです。
      */
      public class TextFormatUtils {

      // ... 前回記述した保存処理 ...

      /**
      * テキストをインポートします。
      *
      * @param doc
      * ドキュメント。nullは不可。
      * @param filepath
      * ファイルパス。nullは不可。
      * @return テキスト。
      * @throws IOException
      * 入出力時のエラー。
      */
      public static JSTreeNode importText(JSDocument doc, String filepath)
      throws IOException {
      if (doc == null || filepath == null) {
      throw new IllegalArgumentException();
      }
      BufferedReader br = null;
      try {
      br = new BufferedReader(new InputStreamReader(new FileInputStream(
      filepath)));
      JSTreeNode rootNode = null;
      JSTreeNode node = null;
      int prevLevel = -1;
      while (true) {
      String line = br.readLine();
      if (line == null) {
      break;
      }
      int level = getLevel(line);
      line = line.trim();

      JSTreeNode newNode = doc.createTreeNode(line);
      if (level == -1) {
      continue;
      } else if (level == 0) {
      rootNode = newNode;
      } else if (prevLevel < level) {
      node.appendChild(newNode);
      } else {
      int delta = prevLevel - level;
      for (int i = 0; i < delta + 1; i++) {
      node = node.getParentNode();
      }
      node.appendChild(newNode);
      }
      node = newNode;
      prevLevel = level;
      }
      return rootNode;
      } finally {
      if (br != null) {
      try {
      br.close();
      } catch (IOException ioe) {
      ;
      }
      br = null;
      }
      }
      }

      /**
      * レベルを取得します。
      *
      * @param text
      * 文字列。nullは不可。
      * @return レベル。
      */
      private static int getLevel(String text) {
      if (text == null) {
      throw new IllegalArgumentException();
      }
      for (int i = 0; i < text.length(); i++) {
      int ch = text.charAt(i);
      if (ch != '\t') {
      return i;
      }
      }
      return -1;
      }

      }

      JavaScriptでのDocumentに対応するクラスとしてJSDocument, TreeNodeに対応するクラスとしてJSTreeNodeを参照しています。これらのオブジェクトの変換はBEITELのJavaScript実行環境によって自動的におこなわれます。

    3. コンパイルし、jarファイルにまとめます。

    4. ext以下にjarファイルを配置します。
      ext直下に配置する必要はなく、サブディレクトリを設けることもできます。

  4. myImportText関数に作成したメソッドへの呼び出し処理を記述します。
    自分で作成したJavaクラスを参照する場合はPackages.(パッケージ名).(クラス名)で参照します。
    var node = Packages.jp.carabiner.beitel.sample.TextFormatUtils.importText(document, path);
    if(targetNode != null) {
    targetNode.appendChild(node);
    }else{
    document.set(path, node);
    }


  5. プラグインをBEITELにインポートします。(beitelのフォルダ)/extの直下に作成したJavaScriptコードを配置します。

  6. BEITELを起動します。jarの更新をおこなった場合は、BEITELの全ウィンドウを閉じ、BEITELを再起動してください。



これで、ファイルメニューと右クリックメニューに[テキスト形式で入力]項目があらわれれば成功です。
クリックし、タブでインデントされた適当なテキストファイルを指定して入力してください。
ドキュメント全体が置き換え、もしくは選択したノードにインポートされれば成功です。


それぞれの処理の詳細についてはヘルプ、APIドキュメントなどを参照してください。

2010年5月18日火曜日

プラグインを作ってみよう:テキスト形式での出力

第2回です。もうちょっと更新頻度上げます。はい。

さて、前回は単純なイベントハンドリングのみでしたが、実用的なプラグインを作るために
  • プラグインへのJavaコードの埋め込み
を見ていきたいと思います。
BEITELのプラグインとJavaコードの連携方法を知ることで、既存のJavaライブラリを活用することが可能になります。


例として、BEITELのデータをテキストファイルに出力する/テキストファイルから読み込むプラグインを考えてみます。
特に今回は、出力を考えることにしましょう。入力に関しては次の回で取り上げます。
(BEITELにははじめからテキスト出力機能が用意されていますが、ほぼ今回のチュートリアルと同じ方法で実現されています。(BEITELのディレクトリ)/ext/text.jsですので、必要に応じて参照してみてください。)

テキストの出力機能は、具体的には、こんなイメージで実装することにしましょう。



以下のようなユーザインタフェースを実装します。
  1. ファイルメニューもしくは右クリックメニューから[テキスト形式で出力]を選択
  2. 保存先ファイルの選択ダイアログが開くのでここから保存先ファイルを選択
  3. 保存処理を実行



では、プラグインを作成していきましょう。
  1. 前回と同様、メニューに対する項目の追加処理をJavaScriptで実装します。
    今回は右クリックメニューだけでなく、ファイルメニューにも項目を追加します。項目選択時の具体的な処理は2.以降で記述していきます。
    なお、文字コードはUTF-8で保存してください。
    window.popup.appendChild(window.popup.createSeparator());
    var comp = window.popup.createMenuItem("テキスト形式で出力", function(ev){
    myExportText(ev.currentTarget);
    });
    window.popup.appendChild(comp);

    window.menu.file.appendChild(window.menu.createSeparator());
    comp = window.menu.createMenuItem("テキスト形式で出力", function(ev){
    myExportText(document.rootNode);
    });
    window.menu.file.appendChild(comp);

    // 出力用関数
    function myExportText(node)
    {
    // テキストへの出力処理
    }

    ここでは、myExportTextという関数を用意し、右クリックメニュー(window.popup)とファイルメニュー(window.menu.file)に区切り線と項目を追加しています。
    使用可能なAPIに関する詳細は、ヘルプを参照してください。

  2. myExportText関数にファイルダイアログを開く処理を実装します。
    JavaScriptからJavaのクラスを呼び出すことができます。
    java.*, javax.*パッケージに対しては (パッケージ名).(クラス名) でインスタンス化、呼び出しが可能です。ここでは、保存ダイアログを開きたいので、javax.swing.JFileChooserを使用し、showSaveDialogメソッドを呼び出すことにします。
    var dlg = new javax.swing.JFileChooser();
    var result = dlg.showSaveDialog(window.swingFrame);
    if(result != javax.swing.JFileChooser.APPROVE_OPTION) {
    return;
    }
    var path = dlg.selectedFile.absolutePath;

    ここでは、現在のフレーム(window.swingFrame)を親として、JFileChooser.showSaveDialogを呼び出しています。結果がキャンセル(APPROVE_OPTION以外)ならば処理を中断し、保存の場合は選択されたファイルの絶対パスを取得し、変数pathに格納しています。
    使用可能なクラスなどは、Java2 SDK 1.6のAPIドキュメントを参照してください。

  3. テキスト出力処理をJavaで実装します。

    1. クラスパスにBEITELのクラスライブラリを追加します。
      今回は選択されたTreeNodeオブジェクトをJavaコードに取り込む必要があるため、beitel.jarをクラスパスに追加する必要があります。
      (BEITELのディレクトリ)/lib/beitel.jarをクラスパスに追加してください。

    2. Javaコードを記述します。
      JavaScriptからjava.io.PrintStreamなどのインスタンスを作成し、出力する処理を記述してもよいのですが、ここではサンプルとしてJavaで記述してみましょう。
      jp.carabiner.beitel.sample.TextFormatUtilsクラスを作成し、ここに静的メソッドとしてテキスト保存メソッドexportText(TreeNode, path)を実装することにします。
      package jp.carabiner.beitel.sample;

      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.io.PrintStream;

      import jp.carabiner.treeeditor.TreeNode;
      import jp.carabiner.treeeditor.js.model.JSTreeNode;

      /**
      * テキスト形式を処理するユーティリティです。
      */
      public class TextFormatUtils {

      /**
      * ファイルに対して文字列としてエキスポートします。
      *
      * @param node
      * ノード。nullは不可。
      * @param filepath
      * ファイルパス。nullは不可。
      * @throws IOException
      * 入出力時のエラー。
      */
      public static void exportText(JSTreeNode node, String filepath)
      throws IOException {
      if (node == null || filepath == null) {
      throw new IllegalArgumentException();
      }
      PrintStream ps = new PrintStream(new FileOutputStream(filepath));
      writeNode(node.getCore(), ps, 0);
      ps.close();
      }

      /**
      * ノードを書き出します。
      *
      * @param node
      * ノード。nullは不可。
      * @param ps
      * 出力先。nullは不可。
      * @param level
      * レベル。
      */
      private static void writeNode(TreeNode node, PrintStream ps, int level) {
      if (node == null || ps == null) {
      throw new IllegalArgumentException();
      }
      for (int i = 0; i < level; i++) {
      ps.print("\t");
      }
      ps.println(node.getText());
      for (int c = 0; c < node.getChildrenCount(); c++) {
      TreeNode cnode = node.getChild(c);
      writeNode(cnode, ps, level + 1);
      }
      }
      }

      JSTreeNodeはJavaScriptのTreeNodeオブジェクトに対応するJavaクラスであり、getCore()メソッドでデータを保持しているJavaオブジェクトを取得することができます。
      このオブジェクトについては特にリファレンスはありませんが、基本的な構造はJavaScriptオブジェクトと同様です。Eclipseのコード補完機能などを利用して、どのようなメソッドがあるか確認してみてください。

    3. コンパイルし、jarファイルにまとめます。

    4. ext以下にjarファイルを配置します。
      ext直下に配置する必要はなく、サブディレクトリを設けることもできます。

  4. myExportText関数に作成したメソッドへの呼び出し処理を記述します。
    自分で作成したJavaクラスを参照する場合はPackages.(パッケージ名).(クラス名)で参照します。
    Packages.jp.carabiner.beitel.sample.TextFormatUtils.exportText(node, path);


  5. プラグインをBEITELにインポートします。(beitelのフォルダ)/extの直下に作成したJavaScriptコードを配置します。

  6. BEITELを起動します。jarの更新をおこなった場合は、BEITELの全ウィンドウを閉じ、BEITELを再起動してください。



これで、ファイルメニューと右クリックメニューに[テキスト形式で出力]項目があらわれれば成功です。
クリックし、適当なテキストファイルを指定して保存してください。
レベルごとにタブでインデントされたテキストファイルが出力されれば成功です。


次回は、この形式のテキストファイルを読み込む処理を考えてみたいと思います。
それぞれの処理の詳細についてはヘルプ、APIドキュメントなどを参照してください。

2010年5月14日金曜日

BEITELバージョン0.4をリリースしました。

本日、BEITELのバージョンをリリースしました。

変更点は以下のとおりです。


■2010年5月14日 (金) version 0.4
  • ノードに概念「トピック」を追加しました。
  • [バグフィックス] 箇条書き [a.,b.,c.,...] [A.,B.,C.,...] において、ノードを26より多く作成するとエラーが発生する。
  • [バグフィックス] 見出し[第 n 節] を選択すると表示が [第 n 款] と表示されてしまう。

ダウンロードは
アウトラインエディタ BEITELのページ
からどうぞ。

2010年5月6日木曜日

BEITEL バージョン0.3をリリースしました

本日、BEITELのバージョンをリリースしました。

変更点は以下のとおりです。


■2010年5月6日 (木) version 0.3
  • [バグフィックス] レベル1ノード(ルートノード)の兄弟にデータをドロップすると不正な状態になり異常動作することがある。
  • ウィンドウサイズを覚えるように機能を追加しました。
  • 画像の縮小アルゴリズムを改善しました。

ダウンロードは
アウトラインエディタ BEITELのページ
からどうぞ。