WisdomSoft - for your serial experiences.

8.4 書式付テキスト

JEditorPane クラスを用いて HTML を使った書式付きの文書を表示します。

8.4.1 HTMLを表示する

これまで扱ってきたテキスト入力コンポーネントは String 型の文字列情報だけで構成される標準テキストでした。しかし、文字列内の個々の文字ごとに独自の色やフォントの情報を与えたり、あるいは文書の中にイメージなどを配置するには、標準テキストだけでは表現できません。

テキストに色やフォントなど様々な情報が付加されている書式付テキストの代表は Web で広く利用されている HTML 文書でしょう。HTML 文書はテキストに意味づけを行うことができ、文書内にイメージや表を表示することも可能です。さらに、スタイル属性を利用することでテキストに色やフォントなどの具体的なビジュアル情報を追加することもできるため、非常に柔軟な文書を作成することができます。

テキストコンポーネントのデータモデルである Document は要素と属性を提供することができるため、HTML のような文書情報を提供することは難しくありません。JTextField や JTextArea クラスは標準テキストしか認識しませんでしたが、javax.swing.JEditorPane クラスを使うことで HTML 等のデータを表示することができます。

javax.swing.JEditorPane クラス
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
              |
              +--javax.swing.JComponent
                    |
                    +--javax.swing.text.JTextComponent
                          |
                          +--javax.swing.JEditorPane
public class JEditorPane extends JTextComponent

JEditorPane コンポーネントは、標準テキストに加えて HTML と RTF 文書を認識することができます。JEditorPane クラスを使って Web ブラウザを開発するといったことも可能でしょう。この他のデータモデルも拡張すれば表示することができるようになります。

表1 JEditorPane() クラスと文書に関するメソッド(抜粋)
コンストラクタ 解説
public JEditorPane() デフォルトの設定で JEditorPane を生成する。
public JEditorPane(URL initialPage) throws IOException 指定された URL をの文書で JEditorPane を生成する。
public JEditorPane(String url) throws IOExceptionURL 指定されたパスの文書で JEditorPane を生成する。
public JEditorPane(String type, String text) 指定された MIME タイプと文字列で JEditorPane を生成する。
メソッド
public final void setContentType(String type) このエディタが処理するコンテンツタイプを設定する。
public final String getContentType() 現在設定されているコンテンツタイプを返す。
public void setPage(String url) throws IOException 表示されている現在の URL を設定する。
public void setPage(URL page) throws IOException 表示されている現在の URL を設定する。
public URL getPage() 表示されている現在の URL を返す。

JEditorPane クラスが表示する文書は、設定されているコンテンツタイプによって処理方法が判断されます。例えば、HTML 文書を表示する場合は setContentType() メソッドからコンテンツタイプの設定を text/html に設定します。標準テキストであれば text/plain、RTF ならば text/rtf となります。

コード1
import javax.swing.*;

public class Test extends JFrame {
	public static void main(String args[]) {
		String html = "<html>" +
			"<body><h1>Kitty on your lap</h1>" +
			"<hr><p color=#FF0000>萌えろ、俺の小宇宙!</p>" +
			"<a href='http://www.yahoo.co.jp'>リンクもできる</a>" +
		"</html>";

		JEditorPane text = new JEditorPane("text/html" , html);

		JFrame win = new Test();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getContentPane().add(text);
		win.show();
	}
}
実行結果
コード1 実行結果

コード1は、HTML 文書としてデータとなるテキストを JEditorPane クラスのコンストラクタに渡しています。実行結果のように HTML 文書で指定した要素の意味を正しく解釈して表示されます。見出しは大きなフォントで、区切り線は水平線として、段落やハイパーリンクも、それぞれ表示されています。

ただし、JEditorPane クラスは Microsoft Internet Explorer のような再利用可能なブラウザコンポーネントほど万能な存在ではありません。このコンポーネントの目指すべきところは、ブラウザのようにほぼ完璧に動作するコンポーネントになることですが、Java1.4 の時点では HTML 文書の単純な誤りで例外を発生させてしまいます。既存の Web サイトで公開されている HTML 文書の多くは仕様に厳密に従ったものではなく、誤った記述が数多く存在しているため、このままではブラウザとして利用することはできないでしょう。

HTML は単純なテキストで表現することができる文書ですが、これを処理してコンポーネントに表示するまでは膨大な処理を書かなければなりません。JEditorPane クラスは 文書 をパーサーで解析し、適切な Document オブジェクトを生成します。この Document オブジェクトは、以前何度も説明したように要素と属性を提供しているため、このような構造的な文書を表現する方法として大変優れています。

コード2
import java.util.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;

public class Test extends JFrame {
	public static void main(String args[]) {
		String html = "<html>" +
			"<body><h1>見出し1</h1>" +
			"<hr><p>段落</p>" +
			"<table border=1 ID="Table1"><tr><th>列名</th></tr>" +
			"<tr><td>行 データ1</td></tr></table>" +
		"</html>";

		Hashtable table = new Hashtable();
		JEditorPane text = new JEditorPane("text/html" , html);
		Element elements[] = text.getDocument().getRootElements();
		for(int i = 0 ; i < elements.length ; i++) createTree(table , elements[i]);

		JTree tree = new JTree(table);

		JFrame win = new Test();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getContentPane().add(text , BorderLayout.CENTER);
		win.getContentPane().add(tree , BorderLayout.WEST);
		win.show();
	}

	public static void createTree(Hashtable table , Element element) {
		if (!element.isLeaf()) {
			for(int i = 0 ; i < element.getElementCount() ; i++) {
				Element childElem = element.getElement(i);
				if (childElem.isLeaf()) table.put(childElem.getName() , "");
				else {
					Hashtable child = new Hashtable();
					createTree(child , childElem);
					table.put(childElem.getName() + "@" + childElem.hashCode() , child);
				}
			}
		}
		else table.put(element.getName() + "@" + element.hashCode() , "");
	}
}
実行結果
コード2 実行結果

コード2は、JEditorPane コンポーネントを初期化した後の Document を取得し、このデータモデルが保有する要素をツリーに列挙しています。要素は必ず木構造の形になるため、要素の子要素を辿っていくと必ず木構造で全体を表現できるようになります。要素の追跡は createTree() メソッド内で自分自身のメソッドを呼び出す再帰処理を使うことでツリーを作成しています。

HTML データを木構造の Element として構造的に扱いたいという場合を除いて、アプリケーションプログラマにとってはテキストで扱ったほうが簡単でしょう。

8.4.2 ハイパーリンク

JEditorPane クラスでデータを HTML として表示したとき、HTML 内にハイパーリンクが存在する可能性があります。知らない人はいないと思いますが、ハイパーリンクとは、Web 上の他の HTML 文書を参照するリンクのことで、通常のブラウザであればマウスでクリックすることでそのページに移動することができます。

JEditorPane クラスはテキスト入力用のコンポーネントなのでデフォルトでは編集可能となっています。テキストを編集できる状態の場合はハイパーリンクも 1 つのテキストとして扱われていますが、編集不可能な JEditorPane ではハイパーリンクを選択することができます。ハイパーリンクの選択はリスナである javax.swing.event.HyperlinkListenerインタフェースに通知されます。このインタフェースは hyperlinkUpdate() メソッドを宣言しています。

HyperlinkListener インタフェース hyperlinkUpdate() メソッド
public void hyperlinkUpdate(HyperlinkEvent e)

e パラメータには、ハイパーリンクに行われた操作の種類などを提供するイベント引数が渡されます。このイベント引数は javax.swing.event.HyperlinkEvent クラスのオブジェクトです。

javax.swing.event.HyperlinkEvent クラス
java.lang.Object
  |
  +--java.util.EventObject
        |
        +--javax.swing.event.HyperlinkEvent
public class HyperlinkEvent extends EventObject

HyperlinkEvent クラスは、イベントを発生させた Element を返す getSourceElement() メソッド、ハイパーリンクが参照する URL を返す getURL() メソッド、リンクの説明を返す getDescription() メソッド、そしてこのイベントを発生させた操作の種類を表すオブジェクトを返す getEventType() メソッドが定義されています。

HyperlinkEvent クラス getSourceElement() メソッド
public Element getSourceElement()
HyperlinkEvent クラス getURL() メソッド
public URL getURL()
HyperlinkEvent クラス getDescription() メソッド
public String getDescription()
HyperlinkEvent クラス getEventType() メソッド
public HyperlinkEvent.EventType getEventType()

getEventType() メソッドが返すオブジェクトは HyperlinkEvent クラスの内部で定義されている javax.swing.event.HyperlinkEvent.EventType クラスのオブジェクトを返します。このクラスは Object クラスを直接継承し、新しい機能は宣言されず toString() メソッドがオーバーライドされているだけです。フィールドに定数としてイベントの種類を表す自らのオブジェクトを提供しているため、これらの定数と getEventType() メソッドの戻り値を比較してイベントの種類を判断します。

HyperlinkEvent.EventType クラスが公開しているイベントの種類を表すフィールド定数は、ハイパーリンクが選択されたことを表す ACTIVATED、マウスカーソルが入ったことを表す ENTERED、マウスカーソルが外に出たことを表す EXITED の 3 種類です。

リスナは JEditorPane クラスの addHyperlinkListener() メソッドから追加することができます。登録されているリスナを削除するには removeHyperlinkListener() メソッドを、現在登録されているリスナを取得したい場合は getHyperlinkListeners() メソッドを使います。

JEditorPane クラス addHyperlinkListener() メソッド
public void addHyperlinkListener(HyperlinkListener listener)
JEditorPane クラス removeHyperlinkListener() メソッド
public void removeHyperlinkListener(HyperlinkListener listener)
JEditorPane クラス getHyperlinkListeners() メソッド
public HyperlinkListener[] getHyperlinkListeners()

JEditorPane コンポーネントが編集不可能な状態で HTML 文書が表示され、リンクが要求されると登録したリスナに通知されます。

コード3
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class Test extends JFrame implements ActionListener , HyperlinkListener {
	public static void main(String args[]) {
		JFrame win = new Test();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.show();
	}

	JEditorPane html = new JEditorPane();
	JTextField addr = new JTextField("http://");
	public Test() {
		addr.addActionListener(this);
		html.setEditable(false);
		html.addHyperlinkListener(this);

		getContentPane().add(addr , BorderLayout.NORTH);
		getContentPane().add(html);
	}
	public void actionPerformed(ActionEvent e) {
		try { html.setPage(addr.getText()); }
		catch(Exception err) { System.out.println(err); }
	}
	public void hyperlinkUpdate(HyperlinkEvent e) {
		if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) return;
		String url = e.getURL().toString();
		addr.setText(url);

		try { html.setPage(url); }
		catch(Exception err) { System.out.println(err); }
	}
}
実行結果
コード3 実行結果

コード3は、ウィンドウ上部に 1 行テキスト入力コンポーネント JTextField を表示しています。このテキストフィールドに URL を入力して決定すると、ウィンドウに指定した URL の Web ページが表示されます。このプログラムが表示している JEditorPane コンポーネントは setEditable() メソッドに false を指定し編集不可能に設定しています。そのため、表示している HTML 文書のハイパーリンクをクリックするとリスナが呼び出されます。

HyperlinkListener では、発生したイベントの種類を最初に調べ、イベントがハイパーリンクを選択したものであれば、ハイパーリンクに従ってそのページに移動するように試みます。ただし、前述したように JEditorPane の HTML のサポートは不完全なものなので、まともに Web サーフィンを楽しむことはできません。HTML の表示も一般のブラウザとは異なりレイアウト崩れも多く、複雑なコードを保有するページを参照するとすぐに例外を発生させてプログラムが停止してしまうでしょう。