4 * License : The MIT License
5 * Copyright(c) 2008 olyutorskii
8 package jp.sourceforge.jindolf.archiver;
10 import java.io.IOException;
11 import java.io.Writer;
12 import java.text.MessageFormat;
13 import java.util.Calendar;
14 import java.util.GregorianCalendar;
15 import java.util.List;
16 import java.util.TimeZone;
17 import jp.sourceforge.jindolf.parser.DecodeErrorInfo;
18 import jp.sourceforge.jindolf.parser.DecodedContent;
23 public final class XmlUtils{
25 private static final String ORIG_DTD =
26 "http://jindolf.sourceforge.jp/xml/dtd/bbsArchive-110421.dtd";
27 private static final String ORIG_NS =
28 "http://jindolf.sourceforge.jp/xml/ns/501";
29 private static final String ORIG_SCHEME =
30 "http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-110421.xsd";
31 private static final String SCHEMA_NS =
32 "http://www.w3.org/2001/XMLSchema-instance";
34 private static final char BS_CHAR = (char) 0x005c; // Backslash
35 private static final String INDENT_UNIT = "\u0020\u0020";
37 private static final TimeZone TZ_TOKYO =
38 TimeZone.getTimeZone("Asia/Tokyo");
52 * @throws IOException 出力エラー
54 public static void dumpDocType(Writer writer) throws IOException{
55 writer.append("<!DOCTYPE village SYSTEM ");
57 writer.append(ORIG_DTD);
64 * オリジナルNameSpace宣言を出力する。
66 * @throws IOException 出力エラー
68 public static void dumpNameSpaceDecl(Writer writer)
70 attrOut(writer, "xmlns", ORIG_NS);
75 * スキーマNameSpace宣言を出力する。
77 * @throws IOException 出力エラー
79 public static void dumpSiNameSpaceDecl(Writer writer)
81 attrOut(writer, "xmlns:xsi", SCHEMA_NS);
88 * @throws IOException 出力エラー
90 public static void dumpSchemeLocation(Writer writer)
94 ORIG_NS + " " + ORIG_SCHEME);
102 * @param level ネストレベル
103 * @throws IOException 出力エラー
105 public static void indent(Writer writer, int level) throws IOException{
106 for(int ct = 1; ct <= level; ct++){
107 writer.append(INDENT_UNIT);
116 * @throws IOException 出力エラー
118 public static void charRefOut(Writer writer, char chVal)
120 if(chVal == '\u0020'){
121 writer.append(" ");
125 if(chVal == '\u0009'){
126 writer.append("	");
130 int ival = 0xffff & ((int) chVal);
131 String hex = Integer.toHexString(ival);
132 if(hex.length() % 2 != 0) hex = "0" + hex;
134 writer.append("&#x");
145 * @throws IOException 出力エラー
147 public static void dumpInvalidChar(Writer writer, char chVal)
150 hexVal = chVal & 0xff;
151 String hexBin = Integer.toHexString(hexVal);
152 if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
154 char replaceChar = '\ufffd';
155 if('\u0000' <= chVal && chVal <= '\u001f'){
156 replaceChar = (char)( chVal + '\u2400' );
159 writer.append("<rawdata");
162 attrOut(writer, "encoding", "Shift_JIS");
165 attrOut(writer, "hexBin", hexBin);
168 writer.append(replaceChar);
169 writer.append("</rawdata>");
173 * 任意の文字がXML規格上のホワイトスペースに属するか判定する。
175 * @return ホワイトスペースならtrue
177 public static boolean isWhiteSpace(char chVal){
194 * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。
195 * <li>連続したホワイトスペースの2文字目以降は文字参照化される。
196 * <li>スペースでないホワイトスペースは無条件に文字参照化される。
197 * <li>{@literal &, <, >, "}は無条件に文字参照化される。
199 * 参考:XML 1.0 規格 3.3.3節
201 * @param seq CDATA文字列
202 * @throws IOException 出力エラー
204 public static void textOut(Writer writer, CharSequence seq)
206 int len = seq.length();
208 boolean leadSpace = false;
210 for(int pos = 0; pos < len; pos++){
211 char chVal = seq.charAt(pos);
213 if(isWhiteSpace(chVal)){
214 if(pos == 0 || pos >= len - 1 || leadSpace){
215 charRefOut(writer, chVal);
216 }else if(chVal != '\u0020'){
217 charRefOut(writer, chVal);
219 writer.append(chVal);
224 writer.append("&");
225 }else if(chVal == '<'){
226 writer.append("<");
227 }else if(chVal == '>'){
228 writer.append(">");
229 }else if(chVal == '"'){
230 writer.append(""");
231 }else if(chVal == '\''){
232 writer.append("'");
233 }else if(chVal == BS_CHAR){
234 writer.append('\u00a5');
235 }else if(chVal == '\u007e'){
236 writer.append('\u203e');
237 }else if(Character.isISOControl(chVal)){
238 dumpInvalidChar(writer, chVal);
240 writer.append(chVal);
254 * @throws IOException 出力エラー
256 public static void attrOut(Writer writer,
260 StringBuilder newValue = new StringBuilder(value);
261 for(int pt = 0; pt < newValue.length(); pt++){
262 char chVal = newValue.charAt(pt);
263 if(chVal == '\n' || chVal == '\r' || chVal == '\t') continue;
264 if(Character.isISOControl(chVal)){
265 newValue.setCharAt(pt, (char)('\u2400' + chVal));
272 textOut(writer, newValue);
278 * xsd:time形式の時刻属性を出力する。
284 * @throws IOException 出力エラー
286 public static void timeAttrOut(Writer writer,
288 int hour, int minute)
292 .format("{0,number,#00}:{1,number,#00}:00+09:00",
294 attrOut(writer, name, cmtTime);
299 * xsd:gMonthDay形式の日付属性を出力する。
305 * @throws IOException 出力エラー
307 public static void dateAttrOut(Writer writer,
312 MessageFormat.format("--{0,number,#00}-{1,number,#00}+09:00",
314 attrOut(writer, name, dateAttr);
319 * xsd:dateTime形式の日付時刻属性を出力する。
323 * @param epochMs エポック時刻
324 * @throws IOException 出力エラー
326 public static void dateTimeAttr(Writer writer,
330 Calendar calendar = new GregorianCalendar(TZ_TOKYO);
332 calendar.setTimeInMillis(epochMs);
333 int year = calendar.get(Calendar.YEAR);
334 int month = calendar.get(Calendar.MONTH) + 1;
335 int day = calendar.get(Calendar.DATE);
336 int hour = calendar.get(Calendar.HOUR_OF_DAY);
337 int minute = calendar.get(Calendar.MINUTE);
338 int sec = calendar.get(Calendar.SECOND);
339 int msec = calendar.get(Calendar.MILLISECOND);
341 String attrVal = MessageFormat.format(
342 "{0,number,#0000}-{1,number,#00}-{2,number,#00}"
343 +"T{3,number,#00}:{4,number,#00}:{5,number,#00}"
344 +".{6,number,#000}+09:00",
345 year, month, day, hour, minute, sec, msec);
347 attrOut(writer, name, attrVal);
353 * デコードエラー情報をrawdataタグで出力する。
354 * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。
356 * @param errorInfo デコードエラー
357 * @throws IOException 出力エラー
359 public static void dumpErrorInfo(Writer writer,
360 DecodeErrorInfo errorInfo)
363 hexVal = errorInfo.getRawByte1st() & 0xff;
364 if(errorInfo.has2nd()){
366 hexVal |= errorInfo.getRawByte2nd() & 0xff;
369 String hexBin = Integer.toHexString(hexVal);
370 if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
372 char replaceChar = Win31j.getWin31jChar(errorInfo);
374 writer.append("<rawdata");
377 attrOut(writer, "encoding", "Shift_JIS");
380 attrOut(writer, "hexBin", hexBin);
383 writer.append(replaceChar);
384 writer.append("</rawdata>");
390 * デコードエラー込みのテキストを出力する。
392 * @param content テキスト
393 * @throws IOException 出力エラー
395 public static void dumpDecodedContent(Writer writer,
396 DecodedContent content)
398 if( ! content.hasDecodeError() ){
399 textOut(writer, content);
405 List<DecodeErrorInfo> errList = content.getDecodeErrorList();
406 for(DecodeErrorInfo err : errList){
407 int charPos = err.getCharPosition();
408 CharSequence line = content.subSequence(last, charPos);
409 textOut(writer, line);
410 dumpErrorInfo(writer, err);
414 CharSequence line = content.subSequence(last, content.length());
415 textOut(writer, line);
423 * @param villageData 村情報
424 * @throws IOException 出力エラー
426 public static void dumpVillageData(Writer writer,
427 VillageData villageData)
429 writer.append("<?xml");
431 attrOut(writer, "version", "1.0");
433 attrOut(writer, "encoding", "UTF-8");
434 writer.append(" ?>\n\n");
436 writer.append("<!--\n");
437 writer.append(" 人狼BBSアーカイブ\n");
438 writer.append(" http://jindolf.sourceforge.jp/\n");
439 writer.append("-->\n\n");
442 writer.append("\n\n");
444 villageData.dumpXml(writer);
446 writer.append("\n<!-- EOF -->\n");