-/*\r
- * entity converter\r
- *\r
- * Copyright(c) 2009 olyutorskii\r
- * $Id: EntityConverter.java 894 2009-11-04 07:26:59Z olyutorskii $\r
- */\r
-\r
-package jp.sourceforge.jindolf.parser;\r
-\r
-import java.util.regex.Matcher;\r
-import java.util.regex.Pattern;\r
-\r
-/**\r
- * 人狼BBSで用いられる4種類のXHTML文字実体参照の\r
- * 解決を伴う{@link DecodedContent}の切り出しを行う。\r
- * 文字実体参照は{@code > < " &}が対象。\r
- * U+005C(バックスラッシュ)をU+00A5(円通貨)に直す処理も行われる。\r
- * ※ 人狼BBSはShift_JIS(⊃JISX0201)で運営されているので、\r
- * バックスラッシュは登場しないはず。\r
- * ※ が、バックスラッシュを生成するShift_JISデコーダは存在する。\r
- * マルチスレッドには非対応。\r
- */\r
-public class EntityConverter{\r
-\r
- private static final String[][] XCHG_TABLE = {\r
- {">", ">"},\r
- {"<", "<"},\r
- {""", "\""},\r
- {"&", "&"},\r
- {"\u005c\u005c", "\u00a5"},\r
- };\r
-\r
- private static final Pattern XCHG_PATTERN;\r
-\r
- static{\r
- StringBuilder regex = new StringBuilder();\r
- for(String[] xchg : XCHG_TABLE){\r
- String xchgFrom = xchg[0];\r
- if(regex.length() > 0) regex.append('|');\r
- regex.append('(')\r
- .append(Pattern.quote(xchgFrom))\r
- .append(')');\r
- assert xchgFrom.indexOf(DecodedContent.ALTCHAR) < 0;\r
- }\r
- XCHG_PATTERN = Pattern.compile(regex.toString());\r
- }\r
-\r
- private final Matcher matcher = XCHG_PATTERN.matcher("");\r
-\r
- /**\r
- * コンストラクタ。\r
- */\r
- public EntityConverter(){\r
- super();\r
- return;\r
- }\r
-\r
- /**\r
- * 実体参照の変換を行う。\r
- * @param content 変換元文書\r
- * @return 切り出された変換済み文書\r
- */\r
- public DecodedContent convert(DecodedContent content){\r
- return append(null, content, 0, content.length());\r
- }\r
-\r
- /**\r
- * 実体参照の変換を行う。\r
- * @param content 変換元文書\r
- * @param range 範囲指定\r
- * @return 切り出された変換済み文書\r
- * @throws IndexOutOfBoundsException 位置指定に不正があった\r
- */\r
- public DecodedContent convert(DecodedContent content, SeqRange range)\r
- throws IndexOutOfBoundsException{\r
- return append(null, content, range.getStartPos(), range.getEndPos());\r
- }\r
-\r
- /**\r
- * 実体参照の変換を行う。\r
- * @param content 変換元文書\r
- * @param startPos 開始位置\r
- * @param endPos 終了位置\r
- * @return 切り出された変換済み文書\r
- * @throws IndexOutOfBoundsException 位置指定に不正があった\r
- */\r
- public DecodedContent convert(DecodedContent content,\r
- int startPos, int endPos)\r
- throws IndexOutOfBoundsException{\r
- return append(null, content, startPos, endPos);\r
- }\r
-\r
- /**\r
- * 実体参照の変換を行い既存のDecodedContentに追加を行う。\r
- * @param target 追加先文書。nullなら新たな文書が用意される。\r
- * @param content 変換元文書\r
- * @return targetもしくは新規に用意された文書\r
- * @throws IndexOutOfBoundsException 位置指定に不正があった\r
- */\r
- public DecodedContent append(DecodedContent target,\r
- DecodedContent content)\r
- throws IndexOutOfBoundsException{\r
- return append(target, content, 0, content.length());\r
- }\r
-\r
- /**\r
- * 実体参照の変換を行い既存のDecodedContentに追加を行う。\r
- * @param target 追加先文書。nullなら新たな文書が用意される。\r
- * @param content 変換元文書\r
- * @param range 範囲指定\r
- * @return targetもしくは新規に用意された文書\r
- * @throws IndexOutOfBoundsException 位置指定に不正があった\r
- */\r
- public DecodedContent append(DecodedContent target,\r
- DecodedContent content,\r
- SeqRange range )\r
- throws IndexOutOfBoundsException{\r
- return append(target, content,\r
- range.getStartPos(), range.getEndPos());\r
- }\r
-\r
- /**\r
- * 実体参照の変換を行い既存のDecodedContentに追加を行う。\r
- * @param target 追加先文書。nullなら新たな文書が用意される。\r
- * @param content 変換元文書\r
- * @param startPos 開始位置\r
- * @param endPos 終了位置\r
- * @return targetもしくは新規に用意された文書\r
- * @throws IndexOutOfBoundsException 位置指定に不正があった\r
- */\r
- public DecodedContent append(DecodedContent target,\r
- DecodedContent content,\r
- int startPos, int endPos)\r
- throws IndexOutOfBoundsException{\r
- if( startPos > endPos\r
- || startPos < 0\r
- || content.length() < endPos){\r
- throw new IndexOutOfBoundsException();\r
- }\r
-\r
- DecodedContent result;\r
- if(target == null){\r
- result = new DecodedContent(endPos - startPos);\r
- }else{\r
- result = target;\r
- }\r
-\r
- this.matcher.reset(content.getRawContent());\r
- this.matcher.region(startPos, endPos);\r
-\r
- int lastPos = startPos;\r
- while(this.matcher.find()){\r
- int group;\r
- int matchStart = -1;\r
- for(group = 1; group <= XCHG_TABLE.length; group++){\r
- matchStart = this.matcher.start(group);\r
- if(matchStart >= 0) break;\r
- }\r
- int matchEnd = this.matcher.end(group);\r
-\r
- result.append(content, lastPos, matchStart);\r
-\r
- String toStr = XCHG_TABLE[group - 1][1];\r
- result.append(toStr);\r
-\r
- lastPos = matchEnd;\r
- }\r
- result.append(content, lastPos, endPos);\r
-\r
- this.matcher.reset("");\r
-\r
- return result;\r
- }\r
-\r
-}\r
+/*
+ * entity converter
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jindolf.parser;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 人狼BBSで用いられる4種類のXHTML文字実体参照の
+ * 解決を伴う{@link DecodedContent}の切り出しを行う。
+ *
+ * <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 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;
+
+
+ /**
+ * コンストラクタ。
+ * 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){
+ int startPos = 0;
+ int endPos = content.length();
+ return append(null, content, startPos, endPos);
+ }
+
+ /**
+ * 実体参照の変換を行う。
+ * @param content 変換元文書
+ * @param range 範囲指定
+ * @return 切り出された変換済み文書
+ * @throws IndexOutOfBoundsException 位置指定に不正があった
+ */
+ public DecodedContent convert(DecodedContent content, SeqRange range)
+ throws IndexOutOfBoundsException{
+ int startPos = range.getStartPos();
+ int endPos = range.getEndPos();
+ return append(null, content, startPos, endPos);
+ }
+
+ /**
+ * 実体参照の変換を行う。
+ * @param content 変換元文書
+ * @param startPos 開始位置
+ * @param endPos 終了位置
+ * @return 切り出された変換済み文書
+ * @throws IndexOutOfBoundsException 位置指定に不正があった
+ */
+ public DecodedContent convert(DecodedContent content,
+ int startPos, int endPos)
+ throws IndexOutOfBoundsException{
+ return append(null, content, startPos, endPos);
+ }
+
+ /**
+ * 実体参照の変換を行い既存のDecodedContentに追加を行う。
+ * @param target 追加先文書。nullなら新たな文書が用意される。
+ * @param content 変換元文書
+ * @return targetもしくは新規に用意された文書
+ * @throws IndexOutOfBoundsException 位置指定に不正があった
+ */
+ public DecodedContent append(DecodedContent target,
+ DecodedContent content)
+ throws IndexOutOfBoundsException{
+ int startPos = 0;
+ int endPos = content.length();
+ return append(target, content, startPos, endPos);
+ }
+
+ /**
+ * 実体参照の変換を行い既存のDecodedContentに追加を行う。
+ * @param target 追加先文書。nullなら新たな文書が用意される。
+ * @param content 変換元文書
+ * @param range 範囲指定
+ * @return targetもしくは新規に用意された文書
+ * @throws IndexOutOfBoundsException 位置指定に不正があった
+ */
+ public DecodedContent append(DecodedContent target,
+ DecodedContent content,
+ SeqRange range )
+ throws IndexOutOfBoundsException{
+ int startPos = range.getStartPos();
+ int endPos = range.getEndPos();
+ return append(target, content, startPos, endPos);
+ }
+
+ /**
+ * 実体参照の変換を行い既存のDecodedContentに追加を行う。
+ * @param target 追加先文書。nullなら新たな文書が用意される。
+ * @param content 変換元文書
+ * @param startPos 開始位置
+ * @param endPos 終了位置
+ * @return targetもしくは新規に用意された文書
+ * @throws IndexOutOfBoundsException 位置指定に不正があった
+ */
+ public DecodedContent append(DecodedContent target,
+ DecodedContent content,
+ int startPos, int endPos)
+ throws IndexOutOfBoundsException{
+ if( startPos > endPos
+ || startPos < 0
+ || content.length() < endPos){
+ throw new IndexOutOfBoundsException();
+ }
+
+ DecodedContent result;
+ if(target == null){
+ int length = endPos - startPos;
+ result = new DecodedContent(length);
+ }else{
+ result = target;
+ }
+
+ this.matcher.reset(content.getRawContent());
+ this.matcher.region(startPos, endPos);
+
+ int copiedPos = startPos;
+ while(this.matcher.find()){
+ int group = -1;
+ int matchStart = -1;
+ String altTxt = "";
+ for(RegexRep rr : VALUES_CACHE){
+ group = rr.getGroupNo();
+ matchStart = this.matcher.start(group);
+ 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, copiedPos, matchStart);
+ result.append(altTxt);
+
+ copiedPos = matchEnd;
+ }
+ 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;
+ }
+
+ }
+
+}