今回は、前回とは逆に、 XML の DOCTYPE 宣言の文字列から、公開識別子やシステム識別子などの文字列を抜き出すサンプル・コードのメモ。
公開識別子とシステム識別子のどちらもある場合
前回と同様に、まずは「公開識別子 (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><!DOCTYPE html PUBLIC "..." "..."></li> * <li><!DOCTYPE html PUBLIC "..."></li> * <li><!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 宣言の処理なんてあまり行わない」ということを考慮して、上記のようにしてます。