はじめに
仕事でXMLを取り込む事があったんだけどその時に相手が制御コードを仕込んできた。
取り込みには下記のメソッドを使用していた。
Unmarshaller#unmarshal(XMLStreamReader reader, Class declaredType)
XMLの中身を文字列として取得⇒ストリームに変換⇒オブジェクト化という流れだ。
そして下記のエラーが出る。
Message: ドキュメントの要素コンテンツに無効なXML文字(Unicode: 0x2)が見つかりました。
※無効な文字は何個かあったけど省略
で、相手側が直してくれないらしいので文字列置換で無効なXML文字を取り除くことになった。
XMLで使える文字列
上記サイトによるとXMLで使える文字のコード値は下記の6パターンとのこと
① #x9 ⇒ タブ
② #xA ⇒ 改行(LF)
③ #xD ⇒ 改行(CR)
④ [#x20-#xD7FF] ⇒ 半角スペース~ハングル
⑤ [#xE000-#xFFFD] ⇒ 外字~特殊用途文字
⑥ [#x10000-#x10FFFF] ⇒ 線文字B音節文字~未定義
まぁ基本的に使う文字は④に入ってると思って貰えばいい
一応、Unicodeにおける文字とコード値の表を置いておく
Javaでの表現
javaでコード値指定して置換する場合、こういう風に書く。
下記は半角スペースをブランクに置換している。
Matcherとか使ってもいいんだけどとりあえずString#replaceAllでも正規表現は使える。
String str = "XMLを文字列化したもの";
str = str.replaceAll("\\u0020", "");
Javaだと「\x00」で2桁の文字コードで「\u0000」で4桁の文字コードが書ける。
バックラッシュが2つ書いてあるのはエスケープです。
全部4桁で書くとこうなる
① #x9 ⇒ "\u0009"
② #xA ⇒ "\u000A"
③ #xD ⇒ "\u000D"
④ [#x20-#xD7FF] ⇒ "[\u0020-\uD7FF]"
⑤ [#xE000-#xFFFD] ⇒ "[\uE000-\uFFFD]"
待て・・・Unicodeだと5桁以上もあるじゃないか・・・どうやって表現すればいいんだと思ったが
正規表現で複数桁のコード値を指定する方法があった。
⑥ [#x10000-#x10FFFF] ⇒ "[\x{10000}-\x{10FFFF}]"
これでいいらしい。
合体させて拒否る
正規表現はOR判定できるのでパイプでくっつけて、全部否定する。
String str = "XMLを文字列化したもの";
str = str.replaceAll("(?!\\u0009|\\u000A|\\u000D|[\\u0020-\\uD7FF]|[\\uE000-\\uFFFD]|[\\x{10000}-\\x{10FFFF}]).", "");
これで使えない文字を外したうえで取り込みができた。
正規表現の部分で最後に「.」(ドット)を書かないと反応しなくてずっと悩んだけど
とりあえずこれで使えない文字を外すことはできた。
2017/08/02追加
コメントで指摘されたんだけどこれでもできる。
言われて気づいたが、なんでかパイプで区切っておかないといけないと勘違いしていた。
String str = "XMLを文字列化したもの";
str = str.replaceAll("[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD\\x{10000}-\\x{10FFFF}]", "");
最後に
WEBで調べた時に「含まない文字を除去」っていうのが見つからなかったので今回の記事を書きました。
含まない行を検索する方法はいっぱい出てくるんだけどね。