2007年 10月 16日 火曜日 Java HTML Parser 2.0 に関するつづき。使った部分の Java コードを抜粋。
前回お話しした HTML Parser 2.0 についてもう少し
<head> タグ内でいくつかのタグの操作をするのですが、一番行いたかった処理は charset 属性の追加です。
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
というタグを追加することが目的でした。すでに既存の charset 属性がある場合には、それを "UTF-8" で上書きする必要がありました。以下は <meta> タグを追加する部分です charset 属性は String クラスのインスタンス charset を使用しています。
// charset 属性を持った <meta> タグを作成
tn = new TagNode();
tn.setTagName("meta");
tn.setAttribute("http-equiv", "\"content-type\"");
tn.setAttribute("content", "text/html; charset=" + charset);
// <head> タグ内の既存のすべての子ノードを取得
nl = head.getChildren();
// <head> タグが空 (すなわち <head></head>) の場合、
// 改行コード一つ持ったテキストノードを足す
if (nl == null) {
nl = new NodeList();
nl.add(new TextNode("\n"));
}
// 新しく作った <meta> タグを先頭に挿入
nl.prepend(tn);
nl.prepend(new TextNode("\n")); // insert newline after <head>
タグを追加しているところだけを抽出すると、別段どうということもない処理です TagNode() クラスを生成して属性をセットした後に、あらかじめ準備しておいた <head> ノードのツリーに挿入しています。ノードツリーは Node クラスの getChildren() メソッドによって NodeList クラスとして取得できます。このノードリストクラスに add() メソッドや remove() メソッドがあり、ここでは prepend() メソッドを使っています。このメソッドは引数として渡した TagNode クラスを先頭に挿入してくれます。
<head> や <body> ノード準備は、私の作ったクラスのコンストラクタ内で行っています。この部分は Parser クラスの elements() メソッドで説明されているサンプルコードを下敷きにしました。基本的な作りは全くそのまま使いました。リンクのサンプルコードでは URL を Parser クラスの引数として与えていますが、これをファイルパスにすれば手元のファイルを開いてパースするコードになります。
いくつか気づいた点
1. MetaTag クラスが用意されていたが使わなかった
一番最初のコードの部分では TagNode クラスを作って、そのタグ名を "meta" に指定しています。MetaTag クラスが準備されているので new MetaTag() とすれば良さそうだったのですが <METAHTTP-EQUIV...> という具合にタグ名の後に属性名が空白無しで続いてしまったので使うのをやめました。使い方が悪かったのか HTML Parser 2.0 のバグなのか調べてないです。
2. テキストノードの isWhiteSpace() メソッドが使えるかと思ったけど、期待した動作とは違った
charset の挿入処理とは別の部分で、テキストノードが空白文字のみで構成されているかどうかを知る必要があり TextNode に isWhiteSpace() というメソッドを見つけて「これは好都合」と思ったのですが、期待通り動きませんでした。「空白文字のみなら true」だと思っていたら、「空白文字を含んでいたら true」と動くように見えました (それってほとんどすべてのテキストノードで true じゃないのか?)。これでは使えないので java.util.regex.Pattern を使いました。
private Pattern whiteSpaces;
...
whiteSpaces = Pattern.compile("^\\s+$");
...
// 空白文字のみの場合は if 文の中に入らない
// tn は TextNode クラスのインスタンス
// TextNode クラスの getText() メソッドでテキストが取得できます
if (!whiteSpaces.matcher(tn.getText()).matches()) {
...
}
...
3. <html>、<body>、<head> タグを追加したときは setStartPosition("0") を実行する必要があった
今回の処理では、入力ファイルが <head> タグや <body> タグを持たないものがあったので、必要に応じてそれらを追加しましたが普通に追加しても追加したノードが最終結果 (私の場合は標準出力) に出てきませんでした。以下の部分は <head> タグを新たに作って <html> タグの子ノードとして追加しています。あたらに作った <head> タグには、一つの改行コードだけを持ったテキストノードを追加しています ( <head></head> というように一行になってしまうのがイヤだったので)。
/**
* Create <head> if it does not exist, and add <head> node into
* the <html> node.
*/
if (head == null) {
// まずは空っぽの <head> ノードを作る
HeadTag ht = new HeadTag();
ht.setTagName("head");
// 一つの改行コードだけを持ったテキストノードを作る
nl = new NodeList();
nl.add(new TextNode("\n")); // add newline after
tn = new TagNode(); // add tag
tn.setTagName("/head");
nl.add(tn);
// 作ったテキストノードを <head> の子ノードとする
ht.setChildren(nl);
// <html> ノードに作った <head> ノードを追加
nl = html.getChildren();
nl.prepend(head = (Node) ht);
nl.prepend(new TextNode("\n")); // add newline after >html>
// この行がイマイチわかっていない
head.setStartPosition(0);
}
最後の setStartPosition(0); という部分が、じつはよくわかっていないまま挿入してあります。こうすると最終結果に追加したノードが現れるのですが、この行がないと現れません。デバッグすると "0" としてここで設定している値は、"0" を設定しない限り "-1" になっています。
先のエントリでお話ししたとおり、この HTML パーサーはテキストの書式をすべて保持してパースしてくれるので、上記のコードを用いて charset 属性を持った meta タグを追加しても、他の部分の書式は (行頭の空白も含めて) まったく変更されません。これを実現するためなのか、各ノードにはテキストの開始位置が格納されています。あらたにノードを作ったら、その開始位置を "0" に指定しないと現れないようです。でもこれは <html> タグ、<head> タグ、<body> タグの場合だけで、<meta> タグを足した時には必要ありませんでした ... いまだ理解が足りないようです。
参考
Posted by keiichio
( 10月 16日 2007年, 12:18:19 午後 JST )
Permalink
投稿されたコメント [1]
cool
Posted by wow gold on 11月月 03日, 2008年 at 10:41 午前 JST #