4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.sfjp.jindolf.dxchg;
10 import java.io.FileNotFoundException;
11 import java.io.IOException;
12 import java.nio.ByteBuffer;
13 import java.nio.CharBuffer;
14 import java.nio.charset.CharacterCodingException;
15 import java.nio.charset.Charset;
16 import java.nio.charset.CharsetEncoder;
17 import java.util.Collections;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.Locale;
21 import java.util.Properties;
23 import java.util.SortedSet;
24 import java.util.TreeSet;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27 import jp.sfjp.jindolf.ResourceManager;
28 import jp.sfjp.jindolf.data.Avatar;
29 import jp.sfjp.jindolf.log.LogWrapper;
30 import jp.sourceforge.jindolf.corelib.Destiny;
31 import jp.sourceforge.jindolf.corelib.GameRole;
34 * まちゅ氏運営のまとめサイト(wolfbbs)に関する諸々。
36 * @see <a href="http://wolfbbs.jp/">まとめサイト</a>
37 * @see <a href="http://pukiwiki.sourceforge.jp/">PukiWiki</a>
39 public final class WolfBBS{
42 public static final String COMMENTLINE;
44 private static final String WIKICHAR = "#&[]()<>+-*:|~/,'%?";
45 private static final Pattern WIKINAME_PATTERN =
46 Pattern.compile("[A-Z][a-z]+([A-Z])[a-z]+");
48 private static final String FACEICONSET =
49 "resources/wolfbbs/faceIconSet.properties";
50 private static final String ORDER_PREFIX = "iconset.order.";
51 private static final List<FaceIconSet> FACEICONSET_LIST =
52 new LinkedList<FaceIconSet>();
54 private static final Charset CHARSET_EUC = Charset.forName("EUC-JP");
56 private static final String WOLFBBS_URL = "http://wolfbbs.jp/";
58 private static final LogWrapper LOGGER = new LogWrapper();
63 }catch(FileNotFoundException e){
64 throw new ExceptionInInitializerError(e);
67 StringBuilder wikicomment = new StringBuilder();
68 wikicomment.append("// ");
69 while(wikicomment.length() < 72){
70 wikicomment.append('=');
72 wikicomment.append('\n');
73 COMMENTLINE = wikicomment.toString();
82 throw new AssertionError();
88 * @throws FileNotFoundException リソースが不明
90 private static void loadFaceIconSet() throws FileNotFoundException {
91 Properties properties = ResourceManager.getProperties(FACEICONSET);
92 if(properties == null){
93 LOGGER.fatal("顔アイコンセットの読み込みに失敗しました");
94 throw new FileNotFoundException();
97 loadFaceIconSet(properties);
104 * @param properties プロパティ
105 * @throws FileNotFoundException リソースが不明
107 private static void loadFaceIconSet(Properties properties)
108 throws FileNotFoundException {
109 String codeCheck = properties.getProperty("codeCheck");
110 if( codeCheck == null
111 || codeCheck.length() != 1
112 || codeCheck.charAt(0) != '\u72fc'){ // 「狼」
116 +"native2ascii は正しく適用しましたか?");
117 throw new FileNotFoundException();
120 Set<Object> keySet = properties.keySet();
122 SortedSet<Integer> orderSet = new TreeSet<Integer>();
123 for(Object keyObj : keySet){
124 if(keyObj == null) continue;
125 String key = keyObj.toString();
126 if( ! key.startsWith(ORDER_PREFIX) ) continue;
127 key = key.replace(ORDER_PREFIX, "");
130 order = Integer.valueOf(key);
131 }catch(NumberFormatException e){
137 for(Integer orderNum : orderSet){
138 String setName = properties.getProperty(ORDER_PREFIX + orderNum);
139 FaceIconSet iconSet = loadFaceIconSet(properties, setName);
140 FACEICONSET_LIST.add(iconSet);
148 * @param properties プロパティ
149 * @param setName アイコンセット名
152 private static FaceIconSet loadFaceIconSet(Properties properties,
154 String author = properties.getProperty(setName + ".author");
155 String caption = properties.getProperty(setName + ".caption");
156 String urlText = properties.getProperty(setName + ".url");
158 FaceIconSet iconSet = new FaceIconSet(caption, author, urlText);
160 List<Avatar> avatarList = Avatar.getPredefinedAvatarList();
161 for(Avatar avatar : avatarList){
162 String identifier = avatar.getIdentifier();
163 String key = setName + ".iconWiki." + identifier;
164 String wiki = properties.getProperty(key);
165 iconSet.registIconWiki(avatar, wiki);
173 * @return 顔アイコンセットのリスト
175 public static List<FaceIconSet> getFaceIconSetList(){
176 List<FaceIconSet> result =
177 Collections.unmodifiableList(FACEICONSET_LIST);
182 * 任意の文字がWikiの特殊キャラクタか否か判定する。
184 * @return 特殊キャラクタならtrue
186 public static boolean isWikiChar(char ch){
187 if(WIKICHAR.indexOf(ch) < 0) return false;
192 * Wiki特殊文字を数値参照文字でエスケープする。
193 * @param seq Wiki特殊文字を含むかもしれない文字列。
194 * @return エスケープされた文字列
196 public static CharSequence escapeWikiChar(CharSequence seq){
197 StringBuilder result = new StringBuilder();
199 int seqLength = seq.length();
200 for(int pos = 0; pos < seqLength; pos++){
201 char ch = seq.charAt(pos);
204 appendNumCharRef(result, ch);
205 }catch(IOException e){
218 * WikiNameを数値参照文字でエスケープする。
219 * @param seq WikiNameを含むかもしれない文字列
220 * @return エスケープされた文字列。
222 public static CharSequence escapeWikiName(CharSequence seq){
223 StringBuilder result = null;
224 Matcher matcher = WIKINAME_PATTERN.matcher(seq);
227 while(matcher.find(pos)){
228 int matchStart = matcher.start();
229 int matchEnd = matcher.end();
230 int capStart = matcher.start(1);
231 int capEnd = matcher.end(1);
233 if(result == null) result = new StringBuilder();
234 result.append(seq, pos, matchStart);
235 result.append(seq, matchStart, capStart);
237 appendNumCharRef(result, seq.charAt(capStart));
238 }catch(IOException e){
242 result.append(seq, capEnd, matchEnd);
247 if(pos == 0) return seq;
249 result.append(seq, pos, seq.length());
255 * 任意の文字列をWiki表記へ変換する。
259 public static CharSequence escapeWikiSyntax(CharSequence seq){
260 CharSequence result = seq;
261 result = escapeWikiChar(result); // この順番は大事
262 result = escapeWikiName(result);
263 // TODO さらにURLとメールアドレスのエスケープも
268 * ブラケットに入れる文字をエスケープする。
270 * @return エスケープされた文字列
272 public static CharSequence escapeWikiBracket(CharSequence seq){
273 StringBuilder result = new StringBuilder();
275 int seqLength = seq.length();
276 for(int pos = 0; pos < seqLength; pos++){
277 char ch = seq.charAt(pos);
280 case '#': ch = '#'; break;
281 case '&': ch = '&'; break;
282 case '[': ch = '['; break;
283 case ']': ch = ']'; break;
284 case '<': ch = '<'; break;
285 case '>': ch = '>'; break;
294 while(result.length() > 0 && result.charAt(0) == '/'){
295 result.deleteCharAt(0);
298 resultLength = result.length();
299 for(int pos = resultLength - 1; pos >= 0; pos--){
300 char ch = result.charAt(pos);
302 result.deleteCharAt(pos);
305 resultLength = result.length();
306 for(int pos = 1; pos < resultLength - 1; pos++){
307 char ch = result.charAt(pos);
309 result.setCharAt(pos, ':');
313 resultLength = result.length();
314 if(resultLength == 1 && result.charAt(0) == ':'){
315 result.setCharAt(0, ':');
322 * 数値参照文字に変換された文字を追加する。
327 * @throws java.io.IOException 入出力エラー。文字列の場合はありえない。
329 public static Appendable appendNumCharRef(Appendable app, char ch)
334 String hex = Integer.toHexString(ival);
343 * 任意の文字を数値参照文字列に変換する。
348 public static CharSequence toNumCharRef(char ch){
349 StringBuilder result = new StringBuilder(8);
351 appendNumCharRef(result, ch);
352 }catch(IOException e){
364 public static String getTeamWikiColor(GameRole role){
395 public static String getRoleIconWiki(GameRole role){
400 result = "&char(村人,nolink);";
403 result = "&char(人狼,nolink);";
406 result = "&char(占い師,nolink);";
409 result = "&char(霊能者,nolink);";
412 result = "&char(狂人,nolink);";
415 result = "&char(狩人,nolink);";
418 result = "&char(共有者,nolink);";
421 result = "&char(ハムスター人間,nolink);";
437 public static String getDestinyColorWiki(Destiny destiny){
439 if(destiny == Destiny.ALIVE) result = "#ffffff";
440 else result = "#aaaaaa";
445 * そのまままとめサイトパス名に使えそうなシンプルな文字か判定する。
447 * @return まとめサイトパス名に使えそうならtrue
449 private static boolean isSimpleIdToken(char ch){
450 if('0' <= ch && ch <= '9') return true;
451 if('A' <= ch && ch <= 'Z') return true;
452 if('a' <= ch && ch <= 'z') return true;
453 if(ch == '-' || ch == '_') return true;
458 * プレイヤーIDを構成する文字からパス名を組み立てる。
463 private static StringBuilder encodeId(StringBuilder seq, char ch){
464 if(isSimpleIdToken(ch)){
469 CharBuffer cbuf = CharBuffer.allocate(1);
473 CharsetEncoder encoder = CHARSET_EUC.newEncoder();
476 bytebuf = encoder.encode(cbuf);
477 }catch(CharacterCodingException e){
482 int limit = bytebuf.limit();
483 while(bytebuf.position() < limit){
484 int iVal = bytebuf.get();
485 if(iVal < 0) iVal += 0x0100;
486 String hex = Integer.toHexString(iVal).toUpperCase(Locale.JAPAN);
488 if(hex.length() < 2) seq.append('0');
496 * プレイヤーIDからパス名の一部を予測する。
498 * @return .htmlを抜いたパス名
500 private static StringBuilder encodeId(CharSequence id){
501 StringBuilder result = new StringBuilder();
502 int length = id.length();
503 for(int pt = 0; pt < length; pt++){
504 char ch = id.charAt(pt);
505 encodeId(result, ch);
511 * プレイヤーIDからまとめサイト上の個人ページを推測する。
513 * @return 個人ページURL文字列
515 public static String encodeURLFromId(CharSequence id){
516 CharSequence encodedId = encodeId(id);
518 String result = WOLFBBS_URL + encodedId + ".html";