4 * License : The MIT License
\r
5 * Copyright(c) 2008 olyutorskii
\r
8 package jp.sourceforge.jindolf.archiver;
\r
10 import java.io.BufferedOutputStream;
\r
11 import java.io.BufferedWriter;
\r
12 import java.io.File;
\r
13 import java.io.FileOutputStream;
\r
14 import java.io.IOException;
\r
15 import java.io.OutputStream;
\r
16 import java.io.OutputStreamWriter;
\r
17 import java.io.Writer;
\r
18 import java.text.MessageFormat;
\r
19 import java.util.Calendar;
\r
20 import java.util.GregorianCalendar;
\r
21 import java.util.List;
\r
22 import java.util.TimeZone;
\r
23 import jp.sourceforge.jindolf.corelib.LandDef;
\r
24 import jp.sourceforge.jindolf.parser.DecodeErrorInfo;
\r
25 import jp.sourceforge.jindolf.parser.DecodedContent;
\r
30 public final class XmlUtils{
\r
32 public static final String ORIG_DTD =
\r
33 "http://jindolf.sourceforge.jp/xml/dtd/bbsArchive-110421.dtd";
\r
34 public static final String ORIG_NS =
\r
35 "http://jindolf.sourceforge.jp/xml/ns/501";
\r
36 public static final String ORIG_SCHEME =
\r
37 "http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-110421.xsd";
\r
38 public static final String SCHEMA_NS =
\r
39 "http://www.w3.org/2001/XMLSchema-instance";
\r
41 public static final String OUTPATH = "D:\\TEMP\\zxzx\\";
\r
43 private static final char BS_CHAR = (char) 0x005c; // Backslash
\r
44 private static final String INDENT_UNIT = "\u0020\u0020";
\r
46 private static final TimeZone TZ_TOKYO =
\r
47 TimeZone.getTimeZone("Asia/Tokyo");
\r
48 private static final GregorianCalendar calendar =
\r
49 new GregorianCalendar(TZ_TOKYO);
\r
63 * @throws IOException 出力エラー
\r
65 public static void dumpDocType(Writer writer) throws IOException{
\r
66 writer.append("<!DOCTYPE village SYSTEM ");
\r
68 writer.append(ORIG_DTD);
\r
70 writer.append(" >");
\r
75 * オリジナルNameSpace宣言を出力する。
\r
77 * @throws IOException 出力エラー
\r
79 public static void dumpNameSpaceDecl(Writer writer)
\r
81 attrOut(writer, "xmlns", ORIG_NS);
\r
86 * スキーマNameSpace宣言を出力する。
\r
88 * @throws IOException 出力エラー
\r
90 public static void dumpSiNameSpaceDecl(Writer writer)
\r
92 attrOut(writer, "xmlns:xsi", SCHEMA_NS);
\r
99 * @throws IOException 出力エラー
\r
101 public static void dumpSchemeLocation(Writer writer)
\r
102 throws IOException{
\r
104 "xsi:schemaLocation",
\r
105 ORIG_NS + " " + ORIG_SCHEME);
\r
112 * @param writer 出力先
\r
113 * @param level ネストレベル
\r
114 * @throws IOException 出力エラー
\r
116 public static void indent(Writer writer, int level) throws IOException{
\r
117 for(int ct = 1; ct <= level; ct++){
\r
118 writer.append(INDENT_UNIT);
\r
125 * @param writer 出力先
\r
126 * @param chVal 出力文字
\r
127 * @throws IOException 出力エラー
\r
129 public static void charRefOut(Writer writer, char chVal)
\r
130 throws IOException{
\r
131 if(chVal == '\u0020'){
\r
132 writer.append(" ");
\r
136 if(chVal == '\u0009'){
\r
137 writer.append("	");
\r
141 int ival = 0xffff & ((int) chVal);
\r
142 String hex = Integer.toHexString(ival);
\r
143 if(hex.length() % 2 != 0) hex = "0" + hex;
\r
145 writer.append("&#x");
\r
146 writer.append(hex);
\r
147 writer.append(";");
\r
154 * @param writer 出力先
\r
155 * @param chVal 不正文字
\r
156 * @throws IOException 出力エラー
\r
158 public static void dumpInvalidChar(Writer writer, char chVal)
\r
159 throws IOException{
\r
161 hexVal = chVal & 0xff;
\r
162 String hexBin = Integer.toHexString(hexVal);
\r
163 if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
\r
165 char replaceChar = '\ufffd';
\r
166 if('\u0000' <= chVal && chVal <= '\u001f'){
\r
167 replaceChar = (char)( chVal + '\u2400' );
\r
170 writer.append("<rawdata");
\r
172 writer.append(' ');
\r
173 attrOut(writer, "encoding", "Shift_JIS");
\r
175 writer.append(' ');
\r
176 attrOut(writer, "hexBin", hexBin);
\r
178 writer.append(" >");
\r
179 writer.append(replaceChar);
\r
180 writer.append("</rawdata>");
\r
184 * 任意の文字がXML規格上のホワイトスペースに属するか判定する。
\r
186 * @return ホワイトスペースならtrue
\r
188 public static boolean isWhiteSpace(char chVal){
\r
205 * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。
\r
206 * <li>連続したホワイトスペースの2文字目以降は文字参照化される。
\r
207 * <li>スペースでないホワイトスペースは無条件に文字参照化される。
\r
208 * <li>{@literal &, <, >, "}は無条件に文字参照化される。
\r
210 * 参考:XML 1.0 規格 3.3.3節
\r
211 * @param writer 出力先
\r
212 * @param seq CDATA文字列
\r
213 * @throws IOException 出力エラー
\r
215 public static void textOut(Writer writer, CharSequence seq)
\r
216 throws IOException{
\r
217 int len = seq.length();
\r
219 boolean leadSpace = false;
\r
221 for(int pos = 0; pos < len; pos++){
\r
222 char chVal = seq.charAt(pos);
\r
224 if(isWhiteSpace(chVal)){
\r
225 if(pos == 0 || pos >= len - 1 || leadSpace){
\r
226 charRefOut(writer, chVal);
\r
227 }else if(chVal != '\u0020'){
\r
228 charRefOut(writer, chVal);
\r
230 writer.append(chVal);
\r
235 writer.append("&");
\r
236 }else if(chVal == '<'){
\r
237 writer.append("<");
\r
238 }else if(chVal == '>'){
\r
239 writer.append(">");
\r
240 }else if(chVal == '"'){
\r
241 writer.append(""");
\r
242 }else if(chVal == '\''){
\r
243 writer.append("'");
\r
244 }else if(chVal == BS_CHAR){
\r
245 writer.append('\u00a5');
\r
246 }else if(chVal == '\u007e'){
\r
247 writer.append('\u203e');
\r
248 }else if(Character.isISOControl(chVal)){
\r
249 dumpInvalidChar(writer, chVal);
\r
251 writer.append(chVal);
\r
262 * @param writer 出力先
\r
265 * @throws IOException 出力エラー
\r
267 public static void attrOut(Writer writer,
\r
269 CharSequence value)
\r
270 throws IOException{
\r
271 StringBuilder newValue = new StringBuilder(value);
\r
272 for(int pt = 0; pt < newValue.length(); pt++){
\r
273 char chVal = newValue.charAt(pt);
\r
274 if(chVal == '\n' || chVal == '\r' || chVal == '\t') continue;
\r
275 if(Character.isISOControl(chVal)){
\r
276 newValue.setCharAt(pt, (char)('\u2400' + chVal));
\r
280 writer.append(name);
\r
281 writer.append('=');
\r
282 writer.append('"');
\r
283 textOut(writer, newValue);
\r
284 writer.append('"');
\r
289 * xsd:time形式の時刻属性を出力する。
\r
290 * タイムゾーンは「+09:00」固定
\r
291 * @param writer 出力先
\r
295 * @throws IOException 出力エラー
\r
297 public static void timeAttrOut(Writer writer,
\r
299 int hour, int minute)
\r
300 throws IOException{
\r
303 .format("{0,number,#00}:{1,number,#00}:00+09:00",
\r
305 attrOut(writer, name, cmtTime);
\r
310 * xsd:gMonthDay形式の日付属性を出力する。
\r
311 * タイムゾーンは「+09:00」固定
\r
312 * @param writer 出力先
\r
316 * @throws IOException 出力エラー
\r
318 public static void dateAttrOut(Writer writer,
\r
320 int month, int day)
\r
321 throws IOException{
\r
323 MessageFormat.format("--{0,number,#00}-{1,number,#00}+09:00",
\r
325 attrOut(writer, name, dateAttr);
\r
330 * xsd:dateTime形式の日付時刻属性を出力する。
\r
331 * タイムゾーンは「+09:00」固定
\r
332 * @param writer 出力先
\r
334 * @param epochMs エポック時刻
\r
335 * @throws IOException 出力エラー
\r
337 public static void dateTimeAttr(Writer writer,
\r
340 throws IOException{
\r
341 synchronized(calendar){
\r
342 calendar.setTimeInMillis(epochMs);
\r
343 int year = calendar.get(Calendar.YEAR);
\r
344 int month = calendar.get(Calendar.MONTH) + 1;
\r
345 int day = calendar.get(Calendar.DATE);
\r
346 int hour = calendar.get(Calendar.HOUR_OF_DAY);
\r
347 int minute = calendar.get(Calendar.MINUTE);
\r
348 int sec = calendar.get(Calendar.SECOND);
\r
349 int msec = calendar.get(Calendar.MILLISECOND);
\r
351 String attrVal = MessageFormat.format(
\r
352 "{0,number,#0000}-{1,number,#00}-{2,number,#00}"
\r
353 +"T{3,number,#00}:{4,number,#00}:{5,number,#00}"
\r
354 +".{6,number,#000}+09:00",
\r
355 year, month, day, hour, minute, sec, msec);
\r
357 attrOut(writer, name, attrVal);
\r
363 * デコードエラー情報をrawdataタグで出力する。
\r
364 * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。
\r
365 * @param writer 出力先
\r
366 * @param errorInfo デコードエラー
\r
367 * @throws IOException 出力エラー
\r
369 public static void dumpErrorInfo(Writer writer,
\r
370 DecodeErrorInfo errorInfo)
\r
371 throws IOException{
\r
373 hexVal = errorInfo.getRawByte1st() & 0xff;
\r
374 if(errorInfo.has2nd()){
\r
376 hexVal |= errorInfo.getRawByte2nd() & 0xff;
\r
379 String hexBin = Integer.toHexString(hexVal);
\r
380 if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
\r
382 char replaceChar = Win31j.getWin31jChar(errorInfo);
\r
384 writer.append("<rawdata");
\r
386 writer.append(' ');
\r
387 attrOut(writer, "encoding", "Shift_JIS");
\r
389 writer.append(' ');
\r
390 attrOut(writer, "hexBin", hexBin);
\r
392 writer.append(" >");
\r
393 writer.append(replaceChar);
\r
394 writer.append("</rawdata>");
\r
400 * デコードエラー込みのテキストを出力する。
\r
401 * @param writer 出力先
\r
402 * @param content テキスト
\r
403 * @throws IOException 出力エラー
\r
405 public static void dumpDecodedContent(Writer writer,
\r
406 DecodedContent content)
\r
407 throws IOException{
\r
408 if( ! content.hasDecodeError() ){
\r
409 textOut(writer, content);
\r
415 List<DecodeErrorInfo> errList = content.getDecodeErrorList();
\r
416 for(DecodeErrorInfo err : errList){
\r
417 int charPos = err.getCharPosition();
\r
418 CharSequence line = content.subSequence(last, charPos);
\r
419 textOut(writer, line);
\r
420 dumpErrorInfo(writer, err);
\r
421 last = charPos + 1;
\r
424 CharSequence line = content.subSequence(last, content.length());
\r
425 textOut(writer, line);
\r
432 * @param writer 出力先
\r
433 * @param villageData 村情報
\r
434 * @throws IOException 出力エラー
\r
436 public static void dumpVillageData(Writer writer,
\r
437 VillageData villageData)
\r
438 throws IOException{
\r
439 writer.append("<?xml");
\r
440 writer.append(' ');
\r
441 attrOut(writer, "version", "1.0");
\r
442 writer.append(' ');
\r
443 attrOut(writer, "encoding", "UTF-8");
\r
444 writer.append(" ?>\n\n");
\r
446 writer.append("<!--\n");
\r
447 writer.append(" 人狼BBSアーカイブ\n");
\r
448 writer.append(" http://jindolf.sourceforge.jp/\n");
\r
449 writer.append("-->\n\n");
\r
451 dumpDocType(writer);
\r
452 writer.append("\n\n");
\r
454 villageData.dumpXml(writer);
\r
456 writer.append("\n<!-- EOF -->\n");
\r
464 * 村情報を反映した出力ファイル名を生成する。
\r
465 * @param village 村情報
\r
466 * @return XML出力ファイル名
\r
468 public static String createOutFileName(VillageData village){
\r
469 LandDef landDef = village.getLandDef();
\r
470 String landId = landDef.getLandId();
\r
471 int vid = village.getVillageId();
\r
474 MessageFormat.format(
\r
475 "{0}jin_{1}_{2,number,#00000}.xml", OUTPATH, landId, vid);
\r
480 * 村情報を反映した出力ファイルへの文字ストリームを生成する。
\r
481 * @param village 村情報
\r
482 * @return 出力先文字ストリーム
\r
483 * @throws IOException 出力エラー
\r
485 public static Writer createFileWriter(VillageData village)
\r
486 throws IOException{
\r
487 String fname = createOutFileName(village);
\r
488 File file = new File(fname);
\r
490 OutputStream ostream;
\r
491 ostream = new FileOutputStream(file);
\r
492 ostream = new BufferedOutputStream(ostream, 10000);
\r
494 writer = new OutputStreamWriter(ostream, "UTF-8");
\r
495 writer = new BufferedWriter(writer, 10000);
\r