4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.sourceforge.jindolf.parser;
10 import java.util.regex.Matcher;
11 import java.util.regex.Pattern;
14 * 人狼BBSで用いられる4種類のXHTML文字実体参照の
15 * 解決を伴う{@link DecodedContent}の切り出しを行う。
17 * <p>文字実体参照は{@code > < " &}が対象。
19 * <p>U+005C(バックスラッシュ)をU+00A5(円通貨)に直す処理も行われる。
20 * ※ 人狼BBSはShift_JIS(⊃JISX0201)で運営されているので、
22 * ※ が、バックスラッシュを生成するShift_JISデコーダは存在する。
24 * <p>指示によりサロゲートペア上位下位の並びを
26 * {@link java.lang.Character#MIN_SUPPLEMENTARY_CODE_POINT}
27 * {@link java.lang.Character#MAX_CODE_POINT}
31 public class EntityConverter{
33 private static final char DQ_CH = '"';
34 private static final String DQ_STR = Character.toString(DQ_CH);
35 private static final String YEN_STR = "\u00a5";
37 private static final char BS_CH = '\u005c\u005c';
38 private static final String BS_STR = Character.toString(BS_CH);
39 private static final String BS_PATTERN = BS_STR + BS_STR;
41 private static final String UCS4_PATTERN = "[\\x{10000}-\\x{10ffff}]";
43 private static final RegexRep[] VALUES_CACHE = RegexRep.values();
46 private final Matcher matcher = RegexRep.buildMatcher();
47 private final boolean replaceSmp;
54 public EntityConverter(){
61 * @param replaceSmp SMP面文字を代替処理するならtrue
63 public EntityConverter(boolean replaceSmp){
65 this.replaceSmp = replaceSmp;
72 * @param content 変換元文書
73 * @return 切り出された変換済み文書
75 public DecodedContent convert(DecodedContent content){
77 int endPos = content.length();
78 return append(null, content, startPos, endPos);
83 * @param content 変換元文書
85 * @return 切り出された変換済み文書
86 * @throws IndexOutOfBoundsException 位置指定に不正があった
88 public DecodedContent convert(DecodedContent content, SeqRange range)
89 throws IndexOutOfBoundsException{
90 int startPos = range.getStartPos();
91 int endPos = range.getEndPos();
92 return append(null, content, startPos, endPos);
97 * @param content 変換元文書
98 * @param startPos 開始位置
100 * @return 切り出された変換済み文書
101 * @throws IndexOutOfBoundsException 位置指定に不正があった
103 public DecodedContent convert(DecodedContent content,
104 int startPos, int endPos)
105 throws IndexOutOfBoundsException{
106 return append(null, content, startPos, endPos);
110 * 実体参照の変換を行い既存のDecodedContentに追加を行う。
111 * @param target 追加先文書。nullなら新たな文書が用意される。
112 * @param content 変換元文書
113 * @return targetもしくは新規に用意された文書
114 * @throws IndexOutOfBoundsException 位置指定に不正があった
116 public DecodedContent append(DecodedContent target,
117 DecodedContent content)
118 throws IndexOutOfBoundsException{
120 int endPos = content.length();
121 return append(target, content, startPos, endPos);
125 * 実体参照の変換を行い既存のDecodedContentに追加を行う。
126 * @param target 追加先文書。nullなら新たな文書が用意される。
127 * @param content 変換元文書
129 * @return targetもしくは新規に用意された文書
130 * @throws IndexOutOfBoundsException 位置指定に不正があった
132 public DecodedContent append(DecodedContent target,
133 DecodedContent content,
135 throws IndexOutOfBoundsException{
136 int startPos = range.getStartPos();
137 int endPos = range.getEndPos();
138 return append(target, content, startPos, endPos);
142 * 実体参照の変換を行い既存のDecodedContentに追加を行う。
143 * @param target 追加先文書。nullなら新たな文書が用意される。
144 * @param content 変換元文書
145 * @param startPos 開始位置
147 * @return targetもしくは新規に用意された文書
148 * @throws IndexOutOfBoundsException 位置指定に不正があった
150 public DecodedContent append(DecodedContent target,
151 DecodedContent content,
152 int startPos, int endPos)
153 throws IndexOutOfBoundsException{
154 if( startPos > endPos
156 || content.length() < endPos){
157 throw new IndexOutOfBoundsException();
160 DecodedContent result;
162 int length = endPos - startPos;
163 result = new DecodedContent(length);
168 this.matcher.reset(content.getRawContent());
169 this.matcher.region(startPos, endPos);
171 int copiedPos = startPos;
172 while(this.matcher.find()){
176 for(RegexRep rr : VALUES_CACHE){
177 group = rr.getGroupNo();
178 matchStart = this.matcher.start(group);
180 if(rr == RegexRep.UCS4 && ! this.replaceSmp){
181 altTxt = this.matcher.group(group);
183 altTxt = rr.getAltTxt();
189 int matchEnd = this.matcher.end(group);
191 result.append(content, copiedPos, matchStart);
192 result.append(altTxt);
194 copiedPos = matchEnd;
196 result.append(content, copiedPos, endPos);
198 this.matcher.reset("");
207 private static enum RegexRep{
212 QUAT (""", DQ_STR),
213 BS (BS_PATTERN, YEN_STR),
214 UCS4 (UCS4_PATTERN, "?"),
218 private final String regex;
219 private final String altTxt;
224 * @param regex 置換元パターン正規表現
225 * @param altTxt 置換文字列。
227 private RegexRep(String regex, String altTxt){
229 this.altTxt = altTxt;
235 * 全正規表現をOR連結したパターンを生成する。
238 private static Pattern buildPattern(){
239 StringBuilder orRegex = new StringBuilder();
241 for(RegexRep rr : values()){
242 if(orRegex.length() > 0) orRegex.append('|');
244 orRegex.append(rr.regex);
248 Pattern result = Pattern.compile(orRegex.toString());
256 private static Matcher buildMatcher(){
257 Pattern pattern = buildPattern();
258 Matcher result = pattern.matcher("");
267 private String getAltTxt(){
272 * パターン内において占めるグループ番号を返す。
275 private int getGroupNo(){
276 int group = ordinal() + 1;