JinParser 変更履歴
+1.408.7-SNAPSHOT (2016-XX-XX)
+ ・SMP面文字代替処理のオンオフを可能に。
+
1.408.6 (2016-06-17)
・必須環境をJRE7に引き上げ。
・Mavenプラグイン更新。
/*
- * content builder for UTF-8 (UCS2 only)
+ * content builder for UTF-8
*
* License : The MIT License
* Copyright(c) 2010 olyutorskii
* "UTF-8"エンコーディング用デコードハンドラ。
* {@link StreamDecoder}からの通知に従い、
* {@link DecodedContent}へとデコードする。
- * UCS-4はUTF-16エラー扱い。
*/
public class ContentBuilderUCS2 extends ContentBuilder{
/**
- * サロゲートペア文字(上位,下位)をUTF-16BEバイト列に変換する。
- * @param ch 文字
- * @return UTF-8バイト列
- */
- public static byte[] charToUTF16(char ch){
- byte[] result = new byte[2];
- result[0] = (byte) (ch >> 8);
- result[1] = (byte) (ch & 0xff);
-
- return result;
- }
-
-
- /**
* デコード処理の初期化下請。
*/
private void initImpl(){
public void charContent(CharSequence seq)
throws DecodeException{
flushError();
-
- int length = seq.length();
- int copyDone = 0;
-
- for(int pos = 0; pos < length; pos++){
- char ch = seq.charAt(pos);
-
- if( ! Character.isHighSurrogate(ch)
- && ! Character.isLowSurrogate (ch) ){
- continue;
- }
-
- if(copyDone < pos){
- CharSequence chopped = seq.subSequence(copyDone, pos);
- getContent().append(chopped);
- }
-
- copyDone = pos + 1;
-
- byte[] barr = charToUTF16(ch);
- for(byte bval : barr){
- getContent().addDecodeError(bval);
- }
- }
-
- if(copyDone < length){
- CharSequence chopped = seq.subSequence(copyDone, length);
- getContent().append(chopped);
- }
-
+ getContent().append(seq);
return;
}
/**
* 人狼BBSで用いられる4種類のXHTML文字実体参照の
* 解決を伴う{@link DecodedContent}の切り出しを行う。
- * 文字実体参照は{@code > < " &}が対象。
- * U+005C(バックスラッシュ)をU+00A5(円通貨)に直す処理も行われる。
+ *
+ * <p>文字実体参照は{@code > < " &}が対象。
+ *
+ * <p>U+005C(バックスラッシュ)をU+00A5(円通貨)に直す処理も行われる。
* ※ 人狼BBSはShift_JIS(⊃JISX0201)で運営されているので、
* バックスラッシュは登場しないはず。
* ※ が、バックスラッシュを生成するShift_JISデコーダは存在する。
- * マルチスレッドには非対応。
+ *
+ * <p>指示によりサロゲートペア上位下位の並びを
+ * 単一疑問符?に直す処理も可能。
+ * {@link java.lang.Character#MIN_SUPPLEMENTARY_CODE_POINT}
+ * {@link java.lang.Character#MAX_CODE_POINT}
+ *
+ * <p>マルチスレッドには非対応。
*/
public class EntityConverter{
- private static final String[][] XCHG_TABLE = {
- {">", ">"},
- {"<", "<"},
- {""", "\""},
- {"&", "&"},
- {"\u005c\u005c", "\u00a5"},
- };
-
- private static final Pattern XCHG_PATTERN;
-
- static{
- StringBuilder regex = new StringBuilder();
- for(String[] xchg : XCHG_TABLE){
- String xchgFrom = xchg[0];
- if(regex.length() > 0) regex.append('|');
- regex.append('(')
- .append(Pattern.quote(xchgFrom))
- .append(')');
- assert xchgFrom.indexOf(DecodedContent.ALTCHAR) < 0;
- }
- XCHG_PATTERN = Pattern.compile(regex.toString());
- }
+ private static final char DQ_CH = '"';
+ private static final String DQ_STR = Character.toString(DQ_CH);
+ private static final String YEN_STR = "\u00a5";
+
+ private static final char BS_CH = '\u005c\u005c';
+ private static final String BS_STR = Character.toString(BS_CH);
+ private static final String BS_PATTERN = BS_STR + BS_STR;
+
+ private static final String UCS4_PATTERN = "[\\x{10000}-\\x{10ffff}]";
+
+ private static final RegexRep[] VALUES_CACHE = RegexRep.values();
+
+
+ private final Matcher matcher = RegexRep.buildMatcher();
+ private final boolean replaceSmp;
- private final Matcher matcher = XCHG_PATTERN.matcher("");
/**
* コンストラクタ。
+ * SMP面文字の代替処理は行われない。
*/
public EntityConverter(){
+ this(false);
+ return;
+ }
+
+ /**
+ * コンストラクタ。
+ * @param replaceSmp SMP面文字を代替処理するならtrue
+ */
+ public EntityConverter(boolean replaceSmp){
super();
+ this.replaceSmp = replaceSmp;
return;
}
+
/**
* 実体参照の変換を行う。
* @param content 変換元文書
* @return 切り出された変換済み文書
*/
public DecodedContent convert(DecodedContent content){
- return append(null, content, 0, content.length());
+ int startPos = 0;
+ int endPos = content.length();
+ return append(null, content, startPos, endPos);
}
/**
*/
public DecodedContent convert(DecodedContent content, SeqRange range)
throws IndexOutOfBoundsException{
- return append(null, content, range.getStartPos(), range.getEndPos());
+ int startPos = range.getStartPos();
+ int endPos = range.getEndPos();
+ return append(null, content, startPos, endPos);
}
/**
public DecodedContent append(DecodedContent target,
DecodedContent content)
throws IndexOutOfBoundsException{
- return append(target, content, 0, content.length());
+ int startPos = 0;
+ int endPos = content.length();
+ return append(target, content, startPos, endPos);
}
/**
DecodedContent content,
SeqRange range )
throws IndexOutOfBoundsException{
- return append(target, content,
- range.getStartPos(), range.getEndPos());
+ int startPos = range.getStartPos();
+ int endPos = range.getEndPos();
+ return append(target, content, startPos, endPos);
}
/**
DecodedContent result;
if(target == null){
- result = new DecodedContent(endPos - startPos);
+ int length = endPos - startPos;
+ result = new DecodedContent(length);
}else{
result = target;
}
this.matcher.reset(content.getRawContent());
this.matcher.region(startPos, endPos);
- int lastPos = startPos;
+ int copiedPos = startPos;
while(this.matcher.find()){
- int group;
+ int group = -1;
int matchStart = -1;
- for(group = 1; group <= XCHG_TABLE.length; group++){
+ String altTxt = "";
+ for(RegexRep rr : VALUES_CACHE){
+ group = rr.getGroupNo();
matchStart = this.matcher.start(group);
- if(matchStart >= 0) break;
+ if(matchStart >= 0){
+ if(rr == RegexRep.UCS4 && ! this.replaceSmp){
+ altTxt = this.matcher.group(group);
+ }else{
+ altTxt = rr.getAltTxt();
+ }
+ break;
+ }
}
+ assert group >= 1;
int matchEnd = this.matcher.end(group);
- result.append(content, lastPos, matchStart);
-
- String toStr = XCHG_TABLE[group - 1][1];
- result.append(toStr);
+ result.append(content, copiedPos, matchStart);
+ result.append(altTxt);
- lastPos = matchEnd;
+ copiedPos = matchEnd;
}
- result.append(content, lastPos, endPos);
+ result.append(content, copiedPos, endPos);
this.matcher.reset("");
return result;
}
+
+ /**
+ * 文字列置換リスト。
+ */
+ private static enum RegexRep{
+
+ GT (">", ">"),
+ LT ("<", "<"),
+ AMP ("&", "&"),
+ QUAT (""", DQ_STR),
+ BS (BS_PATTERN, YEN_STR),
+ UCS4 (UCS4_PATTERN, "?"),
+ ;
+
+
+ private final String regex;
+ private final String altTxt;
+
+
+ /**
+ * コンストラクタ。
+ * @param regex 置換元パターン正規表現
+ * @param altTxt 置換文字列。
+ */
+ private RegexRep(String regex, String altTxt){
+ this.regex = regex;
+ this.altTxt = altTxt;
+ return;
+ }
+
+
+ /**
+ * 全正規表現をOR連結したパターンを生成する。
+ * @return パターン
+ */
+ private static Pattern buildPattern(){
+ StringBuilder orRegex = new StringBuilder();
+
+ for(RegexRep rr : values()){
+ if(orRegex.length() > 0) orRegex.append('|');
+ orRegex.append('(');
+ orRegex.append(rr.regex);
+ orRegex.append(')');
+ }
+
+ Pattern result = Pattern.compile(orRegex.toString());
+ return result;
+ }
+
+ /**
+ * マッチャを生成する。
+ * @return マッチャ
+ */
+ private static Matcher buildMatcher(){
+ Pattern pattern = buildPattern();
+ Matcher result = pattern.matcher("");
+ return result;
+ }
+
+
+ /**
+ * 置換文字列を返す。
+ * @return 置換文字列
+ */
+ private String getAltTxt(){
+ return this.altTxt;
+ }
+
+ /**
+ * パターン内において占めるグループ番号を返す。
+ * @return グループ番号
+ */
+ private int getGroupNo(){
+ int group = ordinal() + 1;
+ return group;
+ }
+
+ }
+
}
byte[] bdata;
InputStream is;
DecodedContent content;
- List<DecodeErrorInfo> errList;
- DecodeErrorInfo einfo;
cd = cs.newDecoder();
decoder = new StreamDecoder(cd);
decoder.decode(is);
content = cb.getContent();
-// assertEquals(7, content.length());
-// assertEquals("A????\udc11B", content.toString());
- assertEquals(6, content.length());
- assertEquals("A????B", content.toString());
- assertTrue(content.hasDecodeError());
- errList = content.getDecodeErrorList();
- assertEquals(4, errList.size());
- einfo = errList.get(0);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0xd8, einfo.getRawByte1st());
- assertEquals(1, einfo.getCharPosition());
- einfo = errList.get(1);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0x3d, einfo.getRawByte1st());
- assertEquals(2, einfo.getCharPosition());
- einfo = errList.get(2);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0xdc, einfo.getRawByte1st());
- assertEquals(3, einfo.getCharPosition());
- einfo = errList.get(3);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0x11, einfo.getRawByte1st());
- assertEquals(4, einfo.getCharPosition());
-
-
- cd = cs.newDecoder();
- decoder = new StreamDecoder(cd);
- cb = new ContentBuilderUCS2();
- decoder.setDecodeHandler(cb);
- bdata = byteArray("d83d:dc11:0042");
- is = new ByteArrayInputStream(bdata);
- decoder.decode(is);
- content = cb.getContent();
-
- assertEquals(5, content.length());
- assertEquals("????B", content.toString());
- assertTrue(content.hasDecodeError());
- errList = content.getDecodeErrorList();
- assertEquals(4, errList.size());
- einfo = errList.get(0);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0xd8, einfo.getRawByte1st());
- assertEquals(0, einfo.getCharPosition());
- einfo = errList.get(1);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0x3d, einfo.getRawByte1st());
- assertEquals(1, einfo.getCharPosition());
- einfo = errList.get(2);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0xdc, einfo.getRawByte1st());
- assertEquals(2, einfo.getCharPosition());
- einfo = errList.get(3);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0x11, einfo.getRawByte1st());
- assertEquals(3, einfo.getCharPosition());
-
-
- cd = cs.newDecoder();
- decoder = new StreamDecoder(cd);
- cb = new ContentBuilderUCS2();
- decoder.setDecodeHandler(cb);
- bdata = byteArray("0041:d83d:dc11");
- is = new ByteArrayInputStream(bdata);
- decoder.decode(is);
- content = cb.getContent();
-
- assertEquals(5, content.length());
- assertEquals("A????", content.toString());
- assertTrue(content.hasDecodeError());
- errList = content.getDecodeErrorList();
- assertEquals(4, errList.size());
- einfo = errList.get(0);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0xd8, einfo.getRawByte1st());
- assertEquals(1, einfo.getCharPosition());
- einfo = errList.get(1);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0x3d, einfo.getRawByte1st());
- assertEquals(2, einfo.getCharPosition());
- einfo = errList.get(2);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0xdc, einfo.getRawByte1st());
- assertEquals(3, einfo.getCharPosition());
- einfo = errList.get(3);
- assertFalse(einfo.has2nd());
- assertEquals((byte)0x11, einfo.getRawByte1st());
- assertEquals(4, einfo.getCharPosition());
-
-
- return;
- }
-
-
- /**
- * Test of charToUTF8 method, of class ContentBuilderUCS2.
- */
- @Test
- public void testCharToUTF16(){
- System.out.println("charToUTF16");
-
- char ch;
- byte[] result;
-
- ch = '\ud844';
- result = ContentBuilderUCS2.charToUTF16(ch);
-
- assertEquals(2, result.length);
+ assertEquals(4, content.length());
+ assertEquals("A\ud83d\udc11B", content.toString());
return;
}
result = converter.convert(from, range);
assertEquals("bcd", result.toString());
+ from = new DecodedContent();
+ from.append("abcde");
+ try{
+ converter.convert(from, 4, 1);
+ fail();
+ }catch(IndexOutOfBoundsException e){
+ // OK
+ }
+
+ from = new DecodedContent();
+ from.append("abcde");
+ try{
+ converter.convert(from, -1, 4);
+ fail();
+ }catch(IndexOutOfBoundsException e){
+ // OK
+ }
+
+ from = new DecodedContent();
+ from.append("abcde");
+ try{
+ converter.convert(from, 1, 6);
+ fail();
+ }catch(IndexOutOfBoundsException e){
+ // OK
+ }
+
+ from = new DecodedContent();
+ from.append("a\ud83d\udc11b"); // 🐑
+ result = converter.convert(from);
+ assertEquals("a\ud83d\udc11b", result.toString());
+
+ from = new DecodedContent();
+ from.append("a\ud83d\udc11b"); // 🐑
+ EntityConverter repConverter = new EntityConverter(true);
+ result = repConverter.convert(from);
+ assertEquals("a?b", result.toString());
+
return;
}