S-JIS[2009-01-31/2009-02-21] 変更履歴
| htlexer.jar | (144.0kB) | [/2009-02-21] | JDK1.6以降(htlexタスクも含む) |
| htlexer.src.zip | (109.4kB) | [/2009-02-21] | ←ソースはEclipseで添付できる |
| Javadoc | [/2009-02-21] |
|
HtHtmlLexerは、HTMLファイル(HTMLの文字列)を読み込んで解析を行います。
例えば以下のようなHTMLは、
<head> <title>タイトル</title> </head>
普通は字句解析は、「<:タグ開き」「head:文字列」「>:タグ閉じ」「<」「title」「>」「タイトル:文字列」「</」「title」「>」「</」「head」「>」といった単位(トークン)に分解します。
構文解析すると、head要素の中にtitle要素があり、title要素の中には「タイトル」というテキストがある…という様に、HTMLの要素の木構造が出来ます。
HtHtmlLexerでは、「<head>:タグ」「<title>:タグ」「タイトル:テキスト」「</title>:タグ」「</head>:タグ」という単位が順番に並んでいるリストが得られます。
すなわち、字句解析よりは上位の解析を行っていますが、構文解析までは至っていません。それに該当する用語を知らないので、HtHtmlLexerは「Lexer(字句解析)」を名乗っています。
また、それに合わせて、「<head>」「<title>」「タイトル:テキスト」「</title>」「</head>」のことをトークンと呼んでいます。(本来なら「要素」と言うべき?)
HtHtmlLexerでは、タグやタグの属性も解釈対象ですが、厳密に文法に沿った解釈はしません。
自分が「こう解釈したい」と思っている文法になっています(爆)
また、構文解析を行うのではないので、タグの並び順については無頓着です。入れ子になっていないタグや、開始タグの無い終了タグも別に問題ありません。
<strong>これは<em>どう解釈すれば</strong>よい?</em> ←入れ子になっていない
</br> ←開始タグの無い終了タグ
</a href="zzz"> ←終了タグに属性が入っている
→解析仕様
HtHtmlLexerで解釈した結果をそのままHTMLファイル(文字列)として出力すると、入力と全く同じ内容が得られます。
改行の数やタブ(インデント)の変更もありませんし、もちろん“本来あるはずのタグ”(例えば<p>に対する</p>)を補填したりしません。
たとえ解釈を誤っていたとしても、出力は全く同じになるはずです。
世の中にHTMLパーサー(構文解析)はいっぱいある訳ですが、自分で作ろうと思った理由がこれ、すなわち「全く同じ内容が得られること」です。
HTMLパーサーはタグを補填してくれたりして高機能なんですが、たいていはHTMLファイルに出力すると元の形とだいぶ変わってしまうので、それが嫌だったのです。
ちなみに「HtHtmlLexer」の先頭の「Ht」は「ひしだまがてきとーに作った」という意味です(爆)
HtHtmlLexerは概ねHTMLの文法に従おうとしていますが、そうでない部分もあります。
| 例 | 一般?解釈 | HtHtmlLexerによる解釈 | 更新日 |
|---|---|---|---|
</tag> |
終了タグ | 開始タグと終了タグは区別しない。 左記の例は、“タグ開きが「 </」である”タグとなる。 |
|
<tag ////> |
IE6だと「<:タグ開き」「////:属性名」「>:タグ閉じ」と解釈される模様。また、XMLだと不正な文法としてエラー? |
「<:タグ開き」「///:属性名」「/>:タグ閉じ」と解釈する。 |
|
<input type="checkbox" checked> |
「checked」は、属性値が省略されているのではなく、属性名が省略されている(らしい)。 |
属性名が「checked」で、属性値が省略されているものとして扱う。 |
|
<!--本来は---ダメ--> |
コメントは「--」の後に「>」以外が来るとダメらしい。 | 「-->」が来るまでどんな文字列もコメントとして扱う。 | 2009-02-08 |
<script>〜</script> |
スクリプト | HtHtmlLexerは基本的にタグの種類や開始・終了は気にしないが、scriptタグだけは認識する。 すなわち、script要素が終了するまでの間は、コメント「<!-- -->」とCDATAセクション以外は全てテキストとして扱う。 その間のダブルクォーテーションやシングルクォーテーションは文字列を囲むための引用符として認識する。 (つまりJavaScriptにおける文字列指定。その中に「</script>」があってもスクリプトの終了とは認識されない) (HtHtmlLexerはスクリプトの解釈をするものではないので、スクリプトの文法には無頓着) |
import java.io.*; import jp.hishidama.html.lexer.rule.HtLexer; import jp.hishidama.html.lexer.token.ListToken; import jp.hishidama.html.lexer.token.Token;
public class Sample1 {
public static void printHtmlFile(File f) throws IOException {
ListToken list = readHtmlFile(f);
for (Token t : list) { // トークンの一覧を順次処理
String s = t.getText();
System.out.print(s);
}
// System.out.print(list.getText()); でも同じ
}
public static ListToken readHtmlFile(File f) throws IOException {
Reader reader = new InputStreamReader(new FileInputStream(f), "MS932");
HtLexer lexer = new HtLexer(); // まずインスタンス生成
lexer.setTarget(reader); // 読み込むHTMLファイルをセット
ListToken list;
try {
list = lexer.parse(); // 解釈実行
} finally {
lexer.close(); // Readerをクローズ
}
return list;
}
}
HtLexer#parse()で返ってきた直後の結果(トークン)には、行番号が入っていません。[2009-02-03]
Token#calcLine()を呼び出すと、各トークンに行番号が振られます。
calcLine()で計算した後では、getLine()で、そのトークンの始まる行番号が得られます。
public static void printLine(ListToken list) {
list.calcLine(1); // 行番号を1から振る。
// 一番最初のタグの行番号
Token first = list.get(0);
System.out.println(first.getLine());
// 一番最後のタグの行番号
Token last = list.getLast();
System.out.println(last.getLine());
}
import jp.hishidama.html.lexer.token.Tag;
public static void changeTagName(ListToken list) {
for (Token t : list) {
if (t instanceof Tag) {
Tag tag = (Tag) t;
String name = tag.getName(); // タグ名取得
if ("b".equalsIgnoreCase(name)) {
tag.setName("strong"); // タグ名設定
}
}
}
}
bタグをstrongタグに変える。
HtHtmlLexerでは開始タグ・終了タグの区別はしないので、「<b>」でも「</b>」でも「<b
attr="zzz">」でも、「b」という名のタグは全て「strong」に変える。
(逆に、“fontタグのうち、<font size="-1">〜</font>のペアだけを変えたい”というような事はすぐには出来ない。HtHtmlLexerでは、開始タグと終了タグをペアとして認識しない為)
HtHtmlLexerで取得したトークン一覧を構文解析してタグ(要素)のツリーを作成します。[2009-02-14]
ただ、構文解析は難しいので、高度・汎用的なことは全く行っていません。不正なタグの補正なんて無理(爆)
import java.io.*; import jp.hishidama.html.lexer.rule.HtLexer; import jp.hishidama.html.lexer.token.ListToken; import jp.hishidama.html.parser.elem.HtElementUtil; import jp.hishidama.html.parser.elem.HtListElement; import jp.hishidama.html.parser.rule.HtParser; import jp.hishidama.html.parser.rule.HtParserManager;
public static void printParseHtml(File f) throws IOException {
ListToken tlist = readHtmlFile(f); // HtHtmlLexerによるファイル読み込み
tlist.calcLine(1);
HtParser parser = new HtParserManager().getDefaultParser();
HtListElement elist = parser.parse(tlist);
HtElementUtil.dumpTree(elist, -1);
}
HtElementUtil#dumpTree()は、デバッグ出力用を行うメソッドです^^;
以下のような出力になります。
MarkDeclare[1]true: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html> [2]true
<head> [4]true
<meta> [5]true
(/meta) [5]true
〜
<title> [8]true
TextToken[8]true: ひしだまのホームページ(Hishidama's HomePage)
</title> [8]true
<link> [9]true
(/link) [9]true
<style> [10]true
Comment[11]true: <!-- span.chg { } -->
</style> [14]true
</head> [15]true
<body> [17]true
〜
<p> [23]true
<img> [23]true
(/img) [23]true
</p> [23]true
〜
</body> [61]true
</html> [62]true
角括弧[ ]内はファイル内の行番号です。
その直後のtrueは、解析によって確定していることを意味します。
falseの場合、解析が確定しなかった事を意味します。マッチするタグが見つからなかった場合や、内包しているタグが未確定の場合にfalseになります。
(↑この辺りが「デバッグ用」。とりあえず自分のページで試したら、falseが出たのは開始タグと終了タグがマッチしていないケースばかりでした)
<tag>や</tag>はファイル内にあったタグで、(tag)や(/tag)は暗黙の開始・終了を意味します。要するに<tag>〜(/tag)の場合、開始タグしか無いことを表しています。
| 更新日 | 変更内容 |
|---|---|
| 2009-01-31 | とりあえず公開。 |
| 2009-02-01 | htlexタスク系を色々ちょっと修正。 |
| 2009-02-06 | タグとテキストの前にスキップ文字(スペース・タブ・改行)のみの解釈を行うよう修正。 タグとテキストの間にスキップ文字があった場合、今まではテキストの一部だったが、SkipTokenが新たに入るようになった。 |
| 2009-02-08 | トークンのclone()メソッドを実装。 HTMLエスケープの実体参照にnbsp・copy・regを追加。 htlexタスクでバックアップディレクトリーの指定を追加。 |
| 2009-02-14 | HtHtmlParserをとりあえず公開。 |
| 2009-02-21 | htlexタスクにHtHtmlParserを使う機能を追加。 |