4 * Copyright(c) 2008 olyutorskii
\r
5 * $Id: XmlUtils.java 877 2009-10-25 15:16:13Z 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-091001.dtd";
\r
34 public static final String ORIG_NS =
\r
35 "http://jindolf.sourceforge.jp/xml/ns/401";
\r
36 public static final String ORIG_SCHEME =
\r
37 "http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-091001.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 String INDENT_UNIT = "\u0020\u0020";
\r
45 private static final TimeZone TZ_TOKYO =
\r
46 TimeZone.getTimeZone("Asia/Tokyo");
\r
47 private static final GregorianCalendar calendar =
\r
48 new GregorianCalendar(TZ_TOKYO);
\r
53 * @throws IOException 出力エラー
\r
55 public static void dumpDocType(Writer writer) throws IOException{
\r
56 writer.append("<!DOCTYPE village SYSTEM ");
\r
58 writer.append(ORIG_DTD);
\r
60 writer.append(" >");
\r
65 * オリジナルNameSpace宣言を出力する。
\r
67 * @throws IOException 出力エラー
\r
69 public static void dumpNameSpaceDecl(Writer writer)
\r
71 attrOut(writer, "xmlns", ORIG_NS);
\r
76 * スキーマNameSpace宣言を出力する。
\r
78 * @throws IOException 出力エラー
\r
80 public static void dumpSiNameSpaceDecl(Writer writer)
\r
82 attrOut(writer, "xmlns:xsi", SCHEMA_NS);
\r
89 * @throws IOException 出力エラー
\r
91 public static void dumpSchemeLocation(Writer writer)
\r
94 "xsi:schemaLocation",
\r
95 ORIG_NS + " " + ORIG_SCHEME);
\r
102 * @param writer 出力先
\r
103 * @param level ネストレベル
\r
104 * @throws IOException 出力エラー
\r
106 public static void indent(Writer writer, int level) throws IOException{
\r
107 for(int ct = 1; ct <= level; ct++){
\r
108 writer.append(INDENT_UNIT);
\r
115 * @param writer 出力先
\r
116 * @param chVal 出力文字
\r
117 * @throws IOException 出力エラー
\r
119 public static void charRefOut(Writer writer, char chVal)
\r
120 throws IOException{
\r
121 if(chVal == '\u0020'){
\r
122 writer.append(" ");
\r
126 if(chVal == '\u0009'){
\r
127 writer.append("	");
\r
131 int ival = 0xffff & ((int) chVal);
\r
132 String hex = Integer.toHexString(ival);
\r
133 if(hex.length() % 2 != 0) hex = "0" + hex;
\r
135 writer.append("&#x");
\r
136 writer.append(hex);
\r
137 writer.append(";");
\r
144 * @param writer 出力先
\r
145 * @param chVal 不正文字
\r
146 * @throws IOException 出力エラー
\r
148 public static void dumpInvalidChar(Writer writer, char chVal)
\r
149 throws IOException{
\r
151 hexVal = chVal & 0xff;
\r
152 String hexBin = Integer.toHexString(hexVal);
\r
153 if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
\r
155 char replaceChar = '\ufffd';
\r
156 if('\u0000' <= chVal && chVal <= '\u001f'){
\r
157 replaceChar = (char)( chVal + '\u2400' );
\r
160 writer.append("<rawdata");
\r
162 writer.append(' ');
\r
163 attrOut(writer, "encoding", "Shift_JIS");
\r
165 writer.append(' ');
\r
166 attrOut(writer, "hexBin", hexBin);
\r
168 writer.append(" >");
\r
169 writer.append(replaceChar);
\r
170 writer.append("</rawdata>");
\r
174 * 任意の文字がXML規格上のホワイトスペースに属するか判定する。
\r
176 * @return ホワイトスペースならtrue
\r
178 public static boolean isWhiteSpace(char chVal){
\r
195 * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。
\r
196 * <li>連続したホワイトスペースの2文字目以降は文字参照化される。
\r
197 * <li>スペースでないホワイトスペースは無条件に文字参照化される。
\r
198 * <li>{@literal &, <, >, "}は無条件に文字参照化される。
\r
200 * 参考:XML 1.0 規格 3.3.3節
\r
201 * @param writer 出力先
\r
202 * @param seq CDATA文字列
\r
203 * @throws IOException 出力エラー
\r
205 public static void textOut(Writer writer, CharSequence seq)
\r
206 throws IOException{
\r
207 int len = seq.length();
\r
209 boolean leadSpace = false;
\r
211 for(int pos = 0; pos < len; pos++){
\r
212 char chVal = seq.charAt(pos);
\r
214 if(isWhiteSpace(chVal)){
\r
215 if(pos == 0 || pos >= len - 1 || leadSpace){
\r
216 charRefOut(writer, chVal);
\r
217 }else if(chVal != '\u0020'){
\r
218 charRefOut(writer, chVal);
\r
220 writer.append(chVal);
\r
225 writer.append("&");
\r
226 }else if(chVal == '<'){
\r
227 writer.append("<");
\r
228 }else if(chVal == '>'){
\r
229 writer.append(">");
\r
230 }else if(chVal == '"'){
\r
231 writer.append(""");
\r
232 }else if(chVal == '\''){
\r
233 writer.append("'");
\r
234 }else if(chVal == '\u005c\u005c'){
\r
235 writer.append('\u00a5');
\r
236 }else if(chVal == '\u007e'){
\r
237 writer.append('\u203e');
\r
238 }else if(Character.isISOControl(chVal)){
\r
239 dumpInvalidChar(writer, chVal);
\r
241 writer.append(chVal);
\r
252 * @param writer 出力先
\r
255 * @throws IOException 出力エラー
\r
257 public static void attrOut(Writer writer,
\r
259 CharSequence value)
\r
260 throws IOException{
\r
261 StringBuilder newValue = new StringBuilder(value);
\r
262 for(int pt = 0; pt < newValue.length(); pt++){
\r
263 char chVal = newValue.charAt(pt);
\r
264 if(chVal == '\n' || chVal == '\r' || chVal == '\t') continue;
\r
265 if(Character.isISOControl(chVal)){
\r
266 newValue.setCharAt(pt, (char)('\u2400' + chVal));
\r
270 writer.append(name);
\r
271 writer.append('=');
\r
272 writer.append('"');
\r
273 textOut(writer, newValue);
\r
274 writer.append('"');
\r
279 * xsd:time形式の時刻属性を出力する。
\r
280 * タイムゾーンは「+09:00」固定
\r
281 * @param writer 出力先
\r
285 * @throws IOException 出力エラー
\r
287 public static void timeAttrOut(Writer writer,
\r
289 int hour, int minute)
\r
290 throws IOException{
\r
293 .format("{0,number,#00}:{1,number,#00}:00+09:00",
\r
295 attrOut(writer, name, cmtTime);
\r
300 * xsd:gMonthDay形式の日付属性を出力する。
\r
301 * タイムゾーンは「+09:00」固定
\r
302 * @param writer 出力先
\r
306 * @throws IOException 出力エラー
\r
308 public static void dateAttrOut(Writer writer,
\r
310 int month, int day)
\r
311 throws IOException{
\r
313 MessageFormat.format("--{0,number,#00}-{1,number,#00}+09:00",
\r
315 attrOut(writer, name, dateAttr);
\r
320 * xsd:dateTime形式の日付時刻属性を出力する。
\r
321 * タイムゾーンは「+09:00」固定
\r
322 * @param writer 出力先
\r
324 * @param epochMs エポック時刻
\r
325 * @throws IOException 出力エラー
\r
327 public static void dateTimeAttr(Writer writer,
\r
330 throws IOException{
\r
331 synchronized(calendar){
\r
332 calendar.setTimeInMillis(epochMs);
\r
333 int year = calendar.get(Calendar.YEAR);
\r
334 int month = calendar.get(Calendar.MONTH) + 1;
\r
335 int day = calendar.get(Calendar.DATE);
\r
336 int hour = calendar.get(Calendar.HOUR_OF_DAY);
\r
337 int minute = calendar.get(Calendar.MINUTE);
\r
338 int sec = calendar.get(Calendar.SECOND);
\r
339 int msec = calendar.get(Calendar.MILLISECOND);
\r
341 String attrVal = MessageFormat.format(
\r
342 "{0,number,#0000}-{1,number,#00}-{2,number,#00}"
\r
343 +"T{3,number,#00}:{4,number,#00}:{5,number,#00}"
\r
344 +".{6,number,#000}+09:00",
\r
345 year, month, day, hour, minute, sec, msec);
\r
347 attrOut(writer, name, attrVal);
\r
353 * デコードエラー情報をrawdataタグで出力する。
\r
354 * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。
\r
355 * @param writer 出力先
\r
356 * @param errorInfo デコードエラー
\r
357 * @throws IOException 出力エラー
\r
359 public static void dumpErrorInfo(Writer writer,
\r
360 DecodeErrorInfo errorInfo)
\r
361 throws IOException{
\r
363 hexVal = errorInfo.getRawByte1st() & 0xff;
\r
364 if(errorInfo.has2nd()){
\r
365 hexVal = hexVal << 8;
\r
366 hexVal |= errorInfo.getRawByte2nd() & 0xff;
\r
369 String hexBin = Integer.toHexString(hexVal);
\r
370 if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
\r
372 char replaceChar = Win31j.getWin31jChar(errorInfo);
\r
374 writer.append("<rawdata");
\r
376 writer.append(' ');
\r
377 attrOut(writer, "encoding", "Shift_JIS");
\r
379 writer.append(' ');
\r
380 attrOut(writer, "hexBin", hexBin);
\r
382 writer.append(" >");
\r
383 writer.append(replaceChar);
\r
384 writer.append("</rawdata>");
\r
390 * デコードエラー込みのテキストを出力する。
\r
391 * @param writer 出力先
\r
392 * @param content テキスト
\r
393 * @throws IOException 出力エラー
\r
395 public static void dumpDecodedContent(Writer writer,
\r
396 DecodedContent content)
\r
397 throws IOException{
\r
398 if( ! content.hasDecodeError() ){
\r
399 textOut(writer, content);
\r
405 List<DecodeErrorInfo> errList = content.getDecodeErrorList();
\r
406 for(DecodeErrorInfo err : errList){
\r
407 int charPos = err.getCharPosition();
\r
408 CharSequence line = content.subSequence(last, charPos);
\r
409 textOut(writer, line);
\r
410 dumpErrorInfo(writer, err);
\r
411 last = charPos + 1;
\r
414 CharSequence line = content.subSequence(last, content.length());
\r
415 textOut(writer, line);
\r
422 * @param writer 出力先
\r
423 * @param villageData 村情報
\r
424 * @throws IOException 出力エラー
\r
426 public static void dumpVillageData(Writer writer,
\r
427 VillageData villageData)
\r
428 throws IOException{
\r
429 writer.append("<?xml");
\r
430 writer.append(' ');
\r
431 attrOut(writer, "version", "1.0");
\r
432 writer.append(' ');
\r
433 attrOut(writer, "encoding", "UTF-8");
\r
434 writer.append(" ?>\n\n");
\r
436 writer.append("<!--\n");
\r
437 writer.append(" 人狼BBSアーカイブ\n");
\r
438 writer.append(" http://jindolf.sourceforge.jp/\n");
\r
439 writer.append("-->\n\n");
\r
441 dumpDocType(writer);
\r
442 writer.append("\n\n");
\r
444 villageData.dumpXml(writer);
\r
446 writer.append("\n<!-- EOF -->\n");
\r
454 * 村情報を反映した出力ファイル名を生成する。
\r
455 * @param village 村情報
\r
456 * @return XML出力ファイル名
\r
458 public static String createOutFileName(VillageData village){
\r
459 LandDef landDef = village.getLandDef();
\r
460 String landId = landDef.getLandId();
\r
461 int vid = village.getVillageId();
\r
464 MessageFormat.format(
\r
465 "{0}jin_{1}_{2,number,#00000}.xml", OUTPATH, landId, vid);
\r
470 * 村情報を反映した出力ファイルへの文字ストリームを生成する。
\r
471 * @param village 村情報
\r
472 * @return 出力先文字ストリーム
\r
473 * @throws IOException 出力エラー
\r
475 public static Writer createFileWriter(VillageData village)
\r
476 throws IOException{
\r
477 String fname = createOutFileName(village);
\r
478 File file = new File(fname);
\r
480 OutputStream ostream;
\r
481 ostream = new FileOutputStream(file);
\r
482 ostream = new BufferedOutputStream(ostream, 10000);
\r
484 writer = new OutputStreamWriter(ostream, "UTF-8");
\r
485 writer = new BufferedWriter(writer, 10000);
\r
492 private XmlUtils(){
\r