析器を自動生成したい,の巻
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));
    }
  }
}

戻る