package jp.osdn.jindolf.parser;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
* <p>文字実体参照は{@code > < " &}が対象。
*
* <p>U+005C(バックスラッシュ)をU+00A5(円通貨)に直す処理も行われる。
- * ※ 人狼BBSはShift_JIS(⊃JISX0201)で運営されているので、
+ * ※ 人狼BBS(F国以前)はShift_JIS(⊃JISX0201)で運営されているので、
* バックスラッシュは登場しないはず。
* ※ が、バックスラッシュを生成するShift_JISデコーダは存在する。
*
private static final String UCS4_PATTERN = "[\\x{10000}-\\x{10ffff}]";
- private static final RegexCnv GT = new RegexCnv(">", ">");
- private static final RegexCnv LT = new RegexCnv("<", "<");
- private static final RegexCnv AMP = new RegexCnv("&", "&");
- private static final RegexCnv QUAT = new RegexCnv(""", DQ_STR);
- private static final RegexCnv BS = new RegexCnv(BS_PATTERN, YEN_STR);
- private static final RegexCnv UCS4 = new RegexCnv(UCS4_PATTERN, "?");
-
- private static final List<RegexCnv> REGS =
- Arrays.asList(GT, LT, AMP, QUAT, BS, UCS4);
-
- private static final Pattern ORPAT;
-
- static{
- int groupNo = 1;
- StringBuilder orRegex = new StringBuilder();
- for(RegexCnv cnv : REGS){
- if(groupNo > 1) orRegex.append('|');
- orRegex.append('(');
- orRegex.append(cnv.getRegex());
- orRegex.append(')');
- cnv.setGroupNo(groupNo++);
- }
- ORPAT = Pattern.compile(orRegex.toString());
- }
+ private static final RepInfo GT = new RepInfo(">", ">");
+ private static final RepInfo LT = new RepInfo("<", "<");
+ private static final RepInfo AMP = new RepInfo("&", "&");
+ private static final RepInfo QUAT = new RepInfo(""", DQ_STR);
+ private static final RepInfo BS = new RepInfo(BS_PATTERN, YEN_STR);
+ private static final RepInfo UCS4 = new RepInfo(UCS4_PATTERN, "?");
+
+ private final MultiMatcher multiMatcher = new MultiMatcher();
+
+ {
+ this.multiMatcher.putRepInfo(GT, LT, AMP, QUAT, BS, UCS4);
+ }
- private final Matcher matcher;
private final boolean replaceSmp;
*/
public EntityConverter(boolean replaceSmp){
super();
- this.matcher = buildMatcher();
this.replaceSmp = replaceSmp;
return;
}
/**
- * マッチャを生成する。
- *
- * @return マッチャ
- */
- private static Matcher buildMatcher(){
- Matcher result = ORPAT.matcher("");
- return result;
- }
-
-
- /**
- * 実体参照の変換を行う。
+ * XHTML文字実体参照の変換を行う。
*
* @param srcContent 変換元文書
* @return 切り出された変換済み文書
}
/**
- * 実体参照の変換を行う。
+ * XHTML文字実体参照の変換を行う。
*
* @param srcContent 変換元文書
* @param range 範囲指定
}
/**
- * 実体参照の変換を行う。
+ * XHTML文字実体参照の変換を行う。
*
* @param srcContent 変換元文書
* @param startPos 開始位置
}
/**
- * 実体参照の変換を行い既存のDecodedContentに追加を行う。
+ * XHTML文字実体参照の変換を行い既存のDecodedContentに追加を行う。
*
* @param dstContent 追加先文書。nullなら新たな文書が用意される。
* @param srcContent 変換元文書
- * @return targetもしくは新規に用意された文書
+ * @return dstContentもしくは新規に用意された文書
* @throws IndexOutOfBoundsException 位置指定に不正があった
*/
public DecodedContent append(DecodedContent dstContent,
}
/**
- * 実体参照の変換を行い既存のDecodedContentに追加を行う。
+ * XHTML文字実体参照の変換を行い既存のDecodedContentに追加を行う。
*
* @param dstContent 追加先文書。nullなら新たな文書が用意される。
* @param srcContent 変換元文書
* @param range 範囲指定
- * @return targetもしくは新規に用意された文書
+ * @return dstContentもしくは新規に用意された文書
* @throws IndexOutOfBoundsException 位置指定に不正があった
*/
public DecodedContent append(DecodedContent dstContent,
}
/**
- * 実体参照の変換を行い既存のDecodedContentに追加を行う。
+ * XHTML文字実体参照の変換を行い既存のDecodedContentに追加を行う。
*
* @param dstContent 追加先文書。nullなら新たな文書が用意される。
* @param srcContent 変換元文書
* @param startPos 開始位置
* @param endPos 終了位置
- * @return targetもしくは新規に用意された文書
+ * @return dstContentもしくは新規に用意された文書
* @throws IndexOutOfBoundsException 位置指定に不正があった
*/
public DecodedContent append(DecodedContent dstContent,
result = dstContent;
}
- this.matcher.reset(srcContent.getRawContent());
- this.matcher.region(startPos, endPos);
+ CharSequence rawContent = srcContent.getRawContent();
+ this.multiMatcher.setText(rawContent, startPos, endPos);
int copiedPos = startPos;
- while(this.matcher.find()){
- int group = -1;
- int matchStart = -1;
- String altTxt = "";
- for(RegexCnv rc : REGS){
- group = rc.getGroupNo();
- matchStart = this.matcher.start(group);
- if(matchStart >= 0){
- if(rc == UCS4 && ! this.replaceSmp){
- altTxt = this.matcher.group(group);
- }else{
- altTxt = rc.getAltTxt();
- }
- break;
- }
+
+ for(;;){
+ RepInfo repInfo = this.multiMatcher.multiFind();
+ if(repInfo == null) break;
+ if(repInfo == UCS4 && ! this.replaceSmp){
+ continue;
}
- assert group >= 1;
- int matchEnd = this.matcher.end(group);
+ int matchStart = this.multiMatcher.getMatchStart();
+ int matchEnd = this.multiMatcher.getMatchEnd();
result.append(srcContent, copiedPos, matchStart);
+
+ String altTxt = repInfo.getAltTxt();
result.append(altTxt);
copiedPos = matchEnd;
}
- result.append(srcContent, copiedPos, endPos);
- this.matcher.reset("");
+ result.append(srcContent, copiedPos, endPos);
return result;
}
/**
- * 文字列置換リスト。
+ * 同時に複数の正規表現をOR探索するマッチャ。
*/
- private static class RegexCnv{
+ private static class MultiMatcher{
+
+ private static final char REGEX_OR = '|';
+ private static final char REGEX_GRPOPEN = '(';
+ private static final char REGEX_GRPCLOSE = ')';
+
+
+ private List<RepInfo> repInfoList;
+ private Pattern orPattern;
+
+ private Matcher matcher;
+
+ private int matchStart = -1;
+ private int matchEnd = -1;
+
+
+ /**
+ * コンストラクタ。
+ */
+ MultiMatcher(){
+ super();
+ return;
+ }
+
+
+ /**
+ * 置換情報を設定する。
+ *
+ * <p>先頭の置換情報の方が優先的にマッチングされる。
+ *
+ * @param infos 置換情報並び
+ */
+ void putRepInfo(RepInfo... infos){
+ List<RepInfo> list;
+ list = Arrays.asList(infos);
+ list = Collections.unmodifiableList(list);
+ this.repInfoList = list;
+
+ StringBuilder orRegex = new StringBuilder();
+ for(RepInfo repInfo : this.repInfoList){
+ String regex = repInfo.getRegex();
+
+ if(orRegex.length() != 0) orRegex.append(REGEX_OR);
+ orRegex.append(REGEX_GRPOPEN);
+ orRegex.append(regex);
+ orRegex.append(REGEX_GRPCLOSE);
+ }
+ this.orPattern = Pattern.compile(orRegex.toString());
+
+ this.matcher = this.orPattern.matcher("");
+ this.matchStart = -1;
+ this.matchEnd = -1;
+
+ return;
+ }
+
+ /**
+ * 走査対象を設定する。
+ *
+ * @param seq 対象文字列
+ * @param startPos 走査開始位置
+ * @param endPos 走査終了位置
+ * @throws IllegalStateException 置換情報が未設定
+ */
+ void setText(CharSequence seq, int startPos, int endPos){
+ if(this.matcher == null) throw new IllegalStateException();
+
+ this.matcher.reset(seq);
+ this.matcher.region(startPos, endPos);
+
+ this.matchStart = -1;
+ this.matchEnd = -1;
+
+ return;
+ }
+
+ /**
+ * マッチ開始位置を返す。
+ *
+ * @return 開始位置
+ */
+ int getMatchStart(){
+ return this.matchStart;
+ }
+
+ /**
+ * マッチ終了位置を返す。
+ *
+ * @return 終了位置
+ */
+ int getMatchEnd(){
+ return this.matchEnd;
+ }
+
+ /**
+ * 同時に複数の正規表現とのマッチングを試みるための走査を行う。
+ *
+ * <p>マッチングに伴いマッチ開始位置と終了位置が更新される。
+ *
+ * @return 最初にマッチングした正規表現。
+ * マッチングしなければnullを返す。
+ * @throws IllegalStateException 置換情報が未設定
+ */
+ RepInfo multiFind(){
+ if(this.repInfoList == null || this.matcher == null){
+ throw new IllegalStateException();
+ }
+
+ if( ! this.matcher.find()) return null;
+
+ RepInfo result = null;
+
+ int group = 1;
+ for(RepInfo rc : this.repInfoList){
+ this.matchStart = this.matcher.start(group);
+ this.matchEnd = this.matcher.end(group);
+ if(this.matchStart >= 0){
+ result = rc;
+ break;
+ }
+ group++;
+ }
+
+ return result;
+ }
+
+ }
+
+
+ /**
+ * 文字列置換設定。
+ */
+ private static class RepInfo{
private final String regex;
private final String altTxt;
- private int groupNo;
/**
* コンストラクタ。
*
+ * <p>正規表現文字列に前方参照グループ記号()を含めてはならない。
+ *
* @param regex 置換元パターン正規表現
* @param altTxt 置換文字列。
*/
- RegexCnv(String regex, String altTxt){
+ RepInfo(String regex, String altTxt){
this.regex = regex;
this.altTxt = altTxt;
return;
return this.altTxt;
}
- /**
- * パターン内において占めるグループ番号を返す。
- *
- * @return グループ番号
- */
- int getGroupNo(){
- return this.groupNo;
- }
-
- void setGroupNo(int groupNo){
- this.groupNo = groupNo;
- return;
- }
-
}
}