倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

DOCTYPE 宣言を扱う (2):文字列の分割

今回は、前回とは逆に、 XMLDOCTYPE 宣言の文字列から、公開識別子やシステム識別子などの文字列を抜き出すサンプル・コードのメモ。

公開識別子とシステム識別子のどちらもある場合


前回と同様に、まずは「公開識別子 (public ID) とシステム識別子 (system ID) がある場合」にを見ていきます:

<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN"
        "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">

この DOCTYPE 宣言からデータを抜き出すメソッドを parseDTDString(..) として作成します。 返り値は文字列の配列 String[] に、各位置の文字列はそれぞれ次の値を設定することにします:

配列の要素番号 データ サンプルでの値
0 要素名 html
1 公開識別子 -//W3C//DTD XHTML Basic 1.0//EN
2 システム識別子 http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd

実装には java.util.Scanner クラスを用いてみましょう:

    public static String[] parseDtdString(String dtdString){
        
        String[] dtd = new String[3];
        Scanner scan = new Scanner(dtdString);

        // <!DOCTYPE ... PUBLIC "..." "..."> の形の正規表現
        // <!DOCTYPE [1.要素名] PUBLIC "[2.公開識別子]" "[3.システム識別子]">
        scan.findInLine(
            "<!DOCTYPE\\s+(\\w+)\\s+PUBLIC\\s+\"([^\"]+)\"\\s+\"([^\"]+)\"\\s*>");
        MatchResult result = scan.match();
            
        dtd[0] = result.group(1);
        dtd[1] = result.group(2);
        dtd[2] = result.group(3);

        scan.close();
        return dtd;
    }

正規表現の文字列はエスケープやら空白に対するマッチ (\\s+, \\s*) やらで分かりにくいかもしれませんが、書いてる方も同じ(笑)

公開識別子、システム識別子がない場合も扱えるようにする


公開識別子やシステム識別子がない場合も扱えるサンプルコードを見ていきましょう。 扱うのは、以下の4つの場合です:

  • <!DOCTYPE html PUBLIC "publicId" "systemId"> → {"html", "publicId", "systemId"}
  • <!DOCTYPE html PUBLIC "..."> → {"html", "publicId", null}
  • <!DOCTYPE html SYSTEM "..."> → {"html", null, "systemId"}
  • それ以外 → IllegalArgumentException を投げる

実装は、先ほどのサンプルよりは複雑に見えますが、やっていることは同じです:

    /** <!DOCTYPE html PUBLIC "..." "..."> の形の文書型宣言 */
    private static final String DOCTYPE_SCAN_PUBLIC_SYSTEM
        = "<!DOCTYPE\\s+(\\w+)\\s+PUBLIC\\s+\"([^\"]+)\"\\s+\"([^\"]+)\"\\s*>";
    
    /** <!DOCTYPE html PUBLIC "..."> の形の文書型宣言 */
    private static final String DOCTYPE_SCAN_PUBLIC
        = "<!DOCTYPE\\s+(\\w+)\\s+PUBLIC\\s+\"([^\"]+)\"\\s*>";
    
    /** <!DOCTYPE html SYSTEM "..."> の形の文書型宣言 */
    private static final String DOCTYPE_SCAN_SYSTEM
        = "<!DOCTYPE\\s+(\\w+)\\s+SYSTEM\\s+\"([^\"]+)\"\\s*>";
    
    /**
     * Acceptable argument formats:
     * <ul>
     *  <li>&lt;!DOCTYPE html PUBLIC "..." "..."></li>
     *  <li>&lt;!DOCTYPE html PUBLIC "..."></li>
     *  <li>&lt;!DOCTYPE html SYSTEM "..."></li>
     * </ul>
     * @throws IllegalArgumentException 
     *                  if an argument string doesn't match the above formats.
     */
    public static String[] parseDtdString(String dtdString){
        
        String[] dtd = new String[3];
        Scanner scan = new Scanner(dtdString);
        
        if(dtdString.matches(DOCTYPE_SCAN_PUBLIC_SYSTEM)){
            scan.findInLine(DOCTYPE_SCAN_PUBLIC_SYSTEM);
            MatchResult result = scan.match();
            
            dtd[0] = result.group(1);
            dtd[1] = result.group(2);
            dtd[2] = result.group(3);
            
        }else if(dtdString.matches(DOCTYPE_SCAN_PUBLIC)){
            scan.findInLine(DOCTYPE_SCAN_PUBLIC);
            MatchResult result = scan.match();
            
            dtd[0] = result.group(1);
            dtd[1] = result.group(2);
            dtd[2] = null;
            
        }else if(dtdString.matches(DOCTYPE_SCAN_SYSTEM)){
            scan.findInLine(DOCTYPE_SCAN_SYSTEM);
            MatchResult result = scan.match();
            
            dtd[0] = result.group(1);
            dtd[1] = null;
            dtd[2] = result.group(2);
            
        }else{
            throw new IllegalArgumentException(
                    "Unknown format of DOCTYPE appears: "+ dtdString);
        }
        
        scan.close();
        return dtd;
    }

正規表現を事前にコンパイルしておけばパフォーマンスは向上するかも知れませんが、「見やすさ」と「DOCTYPE 宣言の処理なんてあまり行わない」ということを考慮して、上記のようにしてます。