解析器を自動生成したい,の巻
2001.04.08 新規

まず、何と言っても Java コンパイラ・コンパイラである JavaCC を入手してないと話になりません。 まずは本家のサイト( Metamata )から持ってきましょう。
ただし、今回の話は v1.1 という版を対象としてます。

本家からダウンロードできる JavaCC の ドキュメント は、もちろん英語。 それを一部、日本語に翻訳したサイトが JavaCC 文法ファイルの説明 にあるので、これも参考にしましょう。

自動生成されたクラスは、パッチを当てないと日本語を扱えません。 そのパッチを公開してるサイトが JavaCCで日本語を扱う方法 にあります。
Reader クラスを使っても日本語は普通には使えなかったので、 上記サイトで公開されてる v0.8pre1 用のパッチで修正しましょう。

最初に書いたとおり、ここで対象として使用してるのは v1.1 で、 Java 開発環境のバージョンは v1.3.0_02 を使っています。

 

さて。ココまで書いておきながらアレですが、もうテンパッてます。 JavaCC を使用するには BNF 記法に関する知識も必要となりますが、自分自身も勉強中なので説明するのは無茶です。
ということなので、サンプルを用意してみました。 簡単な HTML ファイルを解析して、結果を標準出力するサンプルです。

ソースをダウンロード
コンパイル後の全ファイルをダウンロード


「HTMLAnalyzer.jj」のソースコード
options {
  STATIC=false;
  JAVA_UNICODE_ESCAPE=true;
  UNICODE_INPUT=false;
}

PARSER_BEGIN(HTMLAnalyzer)
import java.util.*;

/**
 * Simple HTML Parser.
 * 
 * @author Musi_chan
 * (Musi_chan@cool.biglobe.ne.jp)
 * @version 2001/04/08 11:06
 */
public class HTMLAnalyzer {
/******************************************************************************
	Fields
******************************************************************************/
  private String htmlTitle;
  private Vector value;
  private String tab;
  private String item;

/******************************************************************************
	Methods
******************************************************************************/
  public void init() {
    value=new Vector(5);
    tab="";
    item="";
  }

  public void setTitle(String title) {
    htmlTitle=title;
  }

  public String getTitle() {
    return htmlTitle;
  }

  public void addValue(String title) {
    value.addElement(tab+title);
  }

  public void addTab() {
    tab+="\t";
  }

  public void removeTab() {
    if(!tab.equals("")) {
      tab=tab.substring(1);
    }
  }

  public void setItemize() {
    item+=" ";
  }

  public void resetItemize() {
    if(!item.equals("")) {
      item=item.substring(1);
    }
  }

  public void addItemizeValue(String title) {
    if(value==null) {
      value=new Vector(5);
    }

    value.addElement(tab+item+"* "+title);
  }

  public Vector getValue() {
    return value;
  }

}
PARSER_END(HTMLAnalyzer)


/* ************************************************************************* */
/* ****************************** Token field ****************************** */
  /** Token(Separators) */
SKIP : {
  " " | "\t" | "\n" | "\r" | "\f"
}

  /** Token(Comment) */
SPECIAL_TOKEN : {
  <COMMENT : "<!--" (~[">"])* "-->">
}

  /** Token(NewLine) */
SPECIAL_TOKEN : {
  <BR : "<br>" | "<BR>">
}

  /** Token(Literals) */
TOKEN : {
  <STRING_LITERAL : (~["<",">","\n","\r","\f"])+>
}

  /** Token(Begin tags) */
TOKEN : {
  <HTML  : "<html>" | "<HTML>"> |
  <HEAD  : "<head>" | "<HEAD>"> |
  <TITLE : "<title>" | "<TITLE>"> |
  <BODY  : "<body>" | "<BODY>"> |
  <P     : "<p>" | "<P>"> |
  <UL    : "<ul>" | "<UL>"> |
  <LI    : "<li>" | "<LI>"> |
  <BQ    : "<blockquote>" | "<BLOCKQUOTE>">
}

  /** Token(End tags) */
TOKEN : {
  <EHTML  : "</html>" | "</HTML>"> |
  <EHEAD  : "</head>" | "</HEAD>"> |
  <ETITLE : "</title>" | "</TITLE>"> |
  <EBODY  : "</body>" | "</BODY>"> |
  <EP     : "</p>" | "</P>"> |
  <EUL    : "</ul>" | "</UL>"> |
  <EBQ    : "</blockquote>" | "</BLOCKQUOTE>">
}

/* *************************************************************************** */
/* ****************************** Analyze field ****************************** */
  /** Analyze html file */
void analyzeHTML() : {
} {
  html() <EOF>
}

  /** HTML */
void html() : {
    Token token;
} {
  <HTML> { init(); }
  <HEAD>
  <TITLE> token=<STRING_LITERAL> <ETITLE> { setTitle(token.image); }
  <EHEAD>
  <BODY>
  tagList()
  <EBODY>
  <EHTML>
}

  /** Tag list */
void tagList() : {
} {
  tag() [tagList()]
}

  /** Tag */
void tag() : {
    Token token;
} {
  token=<STRING_LITERAL> { addValue(token.image); } |
  <P> { addValue(""); } (tag())* <EP> |
  <BQ> { addTab(); } (tag())* <EBQ> { removeTab(); } |
  ulSection()
}

  /** UL */
void ulSection() : {
} {
  <UL> { setItemize(); } (li() | ulSection())* <EUL> { resetItemize(); }
}

  /** LI */
void li() : {
    Token token;
} {
  <LI> token=<STRING_LITERAL> { addItemizeValue(token.image); }
}


「Test.java」のソースコード
import java.io.*;
import java.util.*;

/**
 * Test program for HTML Parser.
 * 
 * @author Musi_chan
 * (Musi_chan@cool.biglobe.ne.jp)
 * @version 2001/04/08 11:06
 */
public class Test {
/******************************************************************************
  フィールド
******************************************************************************/
  /**
   * 使用するファイル保持用インスタンス変数です.
   */
  private File useFile;
  /**
   * ファイル読み出し用のインスタンス変数です.
   */
  private BufferedReader reader;
  /**
   * HTMLファイル解析器を保持するインスタンス変数です.
   */
  private HTMLAnalyzer htmlAna;

/******************************************************************************
  コンストラクタ
******************************************************************************/
  /**
   * コンストラクタです.
   */
  public Test() {
  }

/******************************************************************************
  メソッド
******************************************************************************/
  /**
   * メイン・メソッドです.
   */
  public static void main(String[] args) {
    Test test;

    if(args.length!=1) {
      System.exit(1);
    }
    test=new Test();
    test.setFile("",args[0]);
    if(test.exists()) {
      if(test.loadHTMLFile()) {
        test.printResult();
      }
    }
  }

  /**
   * ファイルを設定するメソッドです.
   * 
   * @param baseDirectoryName ディレクトリ名
   * @param fileName ファイル名
   */
  public void setFile(String baseDirectoryName,String fileName) {
    if(baseDirectoryName.equals("")) {
      useFile=new File(fileName);
    } else {
      useFile=new File(baseDirectoryName,fileName);
    }
  }

  /**
   * ファイルがあるか判定するメソッドです.
   * 
   * @return ファイルがある場合true
   */
  public boolean exists() {
    return useFile.exists();
  }

  /**
   * ロード用のストリームを接続するメソッドです.
   * 
   * @throws FileNotFoundException ファイルがない場合に発生
   */
  public void openStreamForLoad() throws FileNotFoundException {
    // リードストリームを開く
    reader=new BufferedReader(new FileReader(useFile));
  }

  /**
   * ロード用のストリームを閉じるメソッドです.
   */
  public void closeStreamForLoad() {
    if(reader==null) {
      return;
    }

    // リードストリームを閉じる
    try {
      reader.close();
    } catch(IOException ioe) {
    }
  }


  /**
   * HTMLの内容を読み込むメソッドです.
   * 
   * @return 正常終了したらtrue
   */
  public boolean loadHTMLFile() {
    // ストリームを開く
    try {
      openStreamForLoad();
    } catch(IOException ioe) {
      // I/O例外のメッセージ出力
      System.out.println("I/O Exception.");

      return false;
    }

    // HTMLパーサを生成
    htmlAna=new HTMLAnalyzer(reader);
    try {
      // HTMLファイルを解析
      htmlAna.analyzeHTML();
    } catch(ParseException pe) {
      // 解析失敗エラーの出力
      System.out.println(pe.getMessage());
      // ストリームを閉じる
      closeStreamForLoad();

      return false;
    }

    // ストリームを閉じる
    closeStreamForLoad();

    return true;
  }

  /**
   * 読み込んだ内容を出力するメソッドです.
   */
  public void printResult() {
    Vector value;

    value=htmlAna.getValue();
    System.out.println("Title: "+htmlAna.getTitle());
    for(int i=0;i<value.size();i++) {
      System.out.println(""+value.elementAt(i));
    }
  }
}

戻る