2008年4月4日金曜日

etc - Java SAXパーサーで任意のトークン切り出し

SAXパーサーでは中途半端な文字数ずつ渡ってくるので、前回の最後の、指定した個数に満たなかった分を
内部に保持しておかないといけない。なので、StringTokenizerやString#splitでは何とかならない。

ちょっとググって無かったので、適当に作ったのを適当に載せときます。メインの部分が汚ねぇ…。

Java SAXパーサーから渡ってくるテキスト要素を、任意のトークンで、指定した個数ずつ切り出すクラスです。指定した個数ずつ読み込むけど、途中で個数変えるのは想定してないです。

ここまで作っといて言うのも何だけど、メモリなんか気にせず、DOMでまとめて読み込んだほうが圧倒的に楽。



/**
* SAXTokenizer.java
* @author uimac
*/
import java.util.Iterator;

interface SAXTokenizerIterator extends Iterator {
public abstract boolean hasNext(int n);
public abstract boolean hasNext(int n, char delim);
public abstract boolean hasNext(int n, char[] delims);
public abstract Object[] next(int n);
public abstract Object[] next(int n, char delim);
public abstract Object[] next(int n, char[] delims);
}

public class SAXTokenizer implements SAXTokenizerIterator {
private final char[] delims;

/**
* Creates a new instance of SAXTokenizer
*/
public SAXTokenizer(char[] delims) {
this.delims = delims;
}

private char[] ch = null;
private int offset = 0;
private int length = 0;
private int index = 0;
private StringBuffer buf = new StringBuffer();

private String[] res = {""};
private int res_index = 0;

// このメソッドでSAXで渡ってくる文字列を毎回設定する
public void setChars(char[] ch, int ofs, int length) {
this.index = ofs;
this.ch = ch;
this.offset = ofs;
this.length = length;
}

public void reset() {
this.index = 0;
this.offset = 0;
this.length = 0;
this.ch = null;
this.buf = new StringBuffer();
this.res_index = 0;
this.res = new String[1];
this.res[0] = "";
}

// 次のトークンがある場合に true を返します。
public boolean hasNext() {
return hasNext(1, this.delims);
}

// 次のトークンがn個ある場合に true を返します。
public boolean hasNext(int n) {
return hasNext(n, this.delims);
}

// 次のdelimで指定したトークンがn個ある場合に true を返します。
public boolean hasNext(int n, char delim) {
char[] delims = { delim };
return hasNext(n, delims);
}

// 次のdelimsで指定したトークンがn個ある場合に true を返します。
public boolean hasNext(int n, char[] delims) {
int count = 0;

for (int i = this.index; i < offset + length; ++i) {
for (int k = 0; k < delims.length; ++k) {
if (ch[i] == delims[k]) count++;
if (count == n) return true;
}
}

return false;
}

// セットされているdelimsトークンで切り出した文字列を、1個返す
// 無かったらnullを返す
public Object next() {
return next(1);
}

// セットされているdelimsトークンで切り出した文字列を、n個返す
// n個無かったらnullを返す
public Object[] next(int n) {
return next(n, this.delims);
}

// delimトークンで切り出した文字列を、n個返す
// n個無かったらnullを返す
public Object[] next(int n, char delim) {
char[] delims = { delim };
return next(n, delims);
}

// delimsトークンで切り出した文字列を、n個返す
// n個無かったらnullを返す
public Object[] next(int n, char[] delims) {
// サイズ違うので、新たに返り値用文字列バッファ生成
if (res.length != n) {
res = new String[n];
}

// 前回読み込んだところ以降を、空文字で初期化
for (int i = res.length - 1; i >= res_index; --i) {
res[i] = "";
}

boolean isOut = false;
for (; index < offset + length; ++index) {
for (int i = 0; i < delims.length; ++i) {
if (ch[index] == delims[i]) {
String str = buf.toString();
buf.delete(0, buf.length());

if ( (index+1) != offset + length)
++index;
else
isOut = true;

if ( !str.equals("") ) {
res[res_index++] = str;
}

if (res_index == n) {
// 全部そろったので返す
res_index = 0;
return res;
}
}
}

if (!isOut)
buf.append(ch[index]);
}

// 指定した個数に満たなかった
return null;
}

// サポートしてないです
public void remove() {
throw new UnsupportedOperationException();
}

}


使用例


// トークン切り出し用オブジェクト。トークンは複数指定できる。
private SAXTokenizer tokenizer = new SAXTokenizer(",;".toCharArray());
// 要素読み込み中フラグ。
// 被るのがあるときはStackにしたり、フラグ増やしたりしないといけない。
private boolean tagA = false;
private boolean tagB = false;

// SAXの要素タグ読み込みメソッド。
public void startElement(String uri,
String localName,
String qName,
Attributes attributes) {

tagA = qName.equals("tagA");
tagB = qName.equals("tagB");
// 要素毎に読み込むため、ここでリセットかけとく。
tokenizer.reset();
}

// SAXのテキスト読み込みメソッド。中途半端な文字数ずつ渡ってくる。
public void characters(char[] ch,
int offset,
int length) {

// 例1:もともとセットされてるトークンで、3個ずつ切り出し
// 元データは例えばこんな感じ 111,222,333;44,5555,66666;77,88,999; ...
if (tagA) {
tokenizer.setChars(ch, offset, length);
for (String[] nums = (String[])tokenizer.next(3); nums != null; nums = (String[])tokenizer.next(3)) {
// なんか処理とか。ここで、numsには必ず3個入っている。
System.out.println(nums[0] + "," + nums[1] + "," + nums[2] + ";" );
}
}

// 例2:トークンを新たに指定して1つずつ切り出し
// 元データは例えばこんな感じ 111,222;333;444,555,666;77,88;999 ...
if (tagB) {
tokenizer.setChars(ch, offset, length);
for (String[] blocks = (String[])tokenizer.next(1, ';'); blocks != null; blocks = (String[])tokenizer.next(1, ';')) {
// 切り出した文字列から、さらにトークンで分割。StringTokenizerよりsplit使えってSUNが言ってた
// http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/util/StringTokenizer.html
String[] nums = blocks[0].split(",");
// なんか処理とか。ここで、numsには必ずnums.length個入っている。
for (int i = 0; i < nums.length; ++i) {
System.out.print(nums[i] + ",");
}
System.out.println("\n");
}
}
}


あ、あと、ここまで実装しといて言うのもなんだけど、
hasNext()で終了判定すると、最後の切れ目の部分が読み込まれないので、返ってくるのがnullかどうかで判定してます。ここを何とかしたいんだけど、まぁこれはこれでhasNext()が意味通りなので良いか。

0 件のコメント: