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 javax.xml.XMLConstants;
18 import javax.xml.parsers.DocumentBuilder;
19 import javax.xml.parsers.DocumentBuilderFactory;
20 import javax.xml.parsers.ParserConfigurationException;
21 import javax.xml.validation.Schema;
22 import javax.xml.validation.SchemaFactory;
23 import javax.xml.validation.Validator;
24 import jp.sourceforge.jindolf.parser.DecodeErrorInfo;
25 import jp.sourceforge.jindolf.parser.DecodedContent;
26 import org.xml.sax.SAXException;
31 public final class XmlUtils{
33 private static final String ORIG_DTD =
34 "http://jindolf.sourceforge.jp/xml/dtd/bbsArchive-110421.dtd";
35 private static final String ORIG_NS =
36 "http://jindolf.sourceforge.jp/xml/ns/501";
37 private static final String ORIG_SCHEME =
38 "http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-110421.xsd";
39 private static final String SCHEMA_NS =
40 "http://www.w3.org/2001/XMLSchema-instance";
42 private static final char BS_CHAR = (char) 0x005c; // Backslash
43 private static final String INDENT_UNIT = "\u0020\u0020";
45 private static final TimeZone TZ_TOKYO =
46 TimeZone.getTimeZone("Asia/Tokyo");
58 * XML読み込み用DocumentBuilderを生成する。
59 * @return DocumentBuilder
60 * @throws ParserConfigurationException 実装が要求に応えられない。
62 public static DocumentBuilder createDocumentBuilder()
63 throws ParserConfigurationException {
64 DocumentBuilderFactory factory;
65 factory = DocumentBuilderFactory.newInstance();
67 DocumentBuilder builder;
68 builder = factory.newDocumentBuilder();
76 * @throws SAXException 実装が要求に応えられない。
78 public static Validator createValidator() throws SAXException{
79 SchemaFactory factory;
80 String nsuri = XMLConstants.W3C_XML_SCHEMA_NS_URI;
81 factory = SchemaFactory.newInstance(nsuri);
84 schema = factory.newSchema();
86 Validator validator = schema.newValidator();
94 * @throws IOException 出力エラー
96 public static void dumpDocType(Writer writer) throws IOException{
97 writer.append("<!DOCTYPE village SYSTEM ");
99 writer.append(ORIG_DTD);
106 * オリジナルNameSpace宣言を出力する。
108 * @throws IOException 出力エラー
110 public static void dumpNameSpaceDecl(Writer writer)
112 attrOut(writer, "xmlns", ORIG_NS);
117 * スキーマNameSpace宣言を出力する。
119 * @throws IOException 出力エラー
121 public static void dumpSiNameSpaceDecl(Writer writer)
123 attrOut(writer, "xmlns:xsi", SCHEMA_NS);
130 * @throws IOException 出力エラー
132 public static void dumpSchemeLocation(Writer writer)
135 "xsi:schemaLocation",
136 ORIG_NS + " " + ORIG_SCHEME);
144 * @param level ネストレベル
145 * @throws IOException 出力エラー
147 public static void indent(Writer writer, int level) throws IOException{
148 for(int ct = 1; ct <= level; ct++){
149 writer.append(INDENT_UNIT);
158 * @throws IOException 出力エラー
160 public static void charRefOut(Writer writer, char chVal)
162 if(chVal == '\u0020'){
163 writer.append(" ");
167 if(chVal == '\u0009'){
168 writer.append("	");
172 int ival = 0xffff & ((int) chVal);
173 String hex = Integer.toHexString(ival);
174 if(hex.length() % 2 != 0) hex = "0" + hex;
176 writer.append("&#x");
187 * @throws IOException 出力エラー
189 public static void dumpInvalidChar(Writer writer, char chVal)
192 hexVal = chVal & 0xff;
193 String hexBin = Integer.toHexString(hexVal);
194 if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
196 char replaceChar = '\ufffd';
197 if('\u0000' <= chVal && chVal <= '\u001f'){
198 replaceChar = (char)( chVal + '\u2400' );
201 writer.append("<rawdata");
204 attrOut(writer, "encoding", "Shift_JIS");
207 attrOut(writer, "hexBin", hexBin);
210 writer.append(replaceChar);
211 writer.append("</rawdata>");
215 * 任意の文字がXML規格上のホワイトスペースに属するか判定する。
217 * @return ホワイトスペースならtrue
219 public static boolean isWhiteSpace(char chVal){
236 * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。
237 * <li>連続したホワイトスペースの2文字目以降は文字参照化される。
238 * <li>スペースでないホワイトスペースは無条件に文字参照化される。
239 * <li>{@literal &, <, >, "}は無条件に文字参照化される。
241 * 参考:XML 1.0 規格 3.3.3節
243 * @param seq CDATA文字列
244 * @throws IOException 出力エラー
246 public static void textOut(Writer writer, CharSequence seq)
248 int len = seq.length();
250 boolean leadSpace = false;
252 for(int pos = 0; pos < len; pos++){
253 char chVal = seq.charAt(pos);
255 if(isWhiteSpace(chVal)){
256 if(pos == 0 || pos >= len - 1 || leadSpace){
257 charRefOut(writer, chVal);
258 }else if(chVal != '\u0020'){
259 charRefOut(writer, chVal);
261 writer.append(chVal);
266 writer.append("&");
267 }else if(chVal == '<'){
268 writer.append("<");
269 }else if(chVal == '>'){
270 writer.append(">");
271 }else if(chVal == '"'){
272 writer.append(""");
273 }else if(chVal == '\''){
274 writer.append("'");
275 }else if(chVal == BS_CHAR){
276 writer.append('\u00a5');
277 }else if(chVal == '\u007e'){
278 writer.append('\u203e');
279 }else if(Character.isISOControl(chVal)){
280 dumpInvalidChar(writer, chVal);
282 writer.append(chVal);
296 * @throws IOException 出力エラー
298 public static void attrOut(Writer writer,
302 StringBuilder newValue = new StringBuilder(value);
303 for(int pt = 0; pt < newValue.length(); pt++){
304 char chVal = newValue.charAt(pt);
305 if(chVal == '\n' || chVal == '\r' || chVal == '\t') continue;
306 if(Character.isISOControl(chVal)){
307 newValue.setCharAt(pt, (char)('\u2400' + chVal));
314 textOut(writer, newValue);
320 * xsd:time形式の時刻属性を出力する。
326 * @throws IOException 出力エラー
328 public static void timeAttrOut(Writer writer,
330 int hour, int minute)
334 .format("{0,number,#00}:{1,number,#00}:00+09:00",
336 attrOut(writer, name, cmtTime);
341 * xsd:gMonthDay形式の日付属性を出力する。
347 * @throws IOException 出力エラー
349 public static void dateAttrOut(Writer writer,
354 MessageFormat.format("--{0,number,#00}-{1,number,#00}+09:00",
356 attrOut(writer, name, dateAttr);
361 * xsd:dateTime形式の日付時刻属性を出力する。
365 * @param epochMs エポック時刻
366 * @throws IOException 出力エラー
368 public static void dateTimeAttr(Writer writer,
372 Calendar calendar = new GregorianCalendar(TZ_TOKYO);
374 calendar.setTimeInMillis(epochMs);
375 int year = calendar.get(Calendar.YEAR);
376 int month = calendar.get(Calendar.MONTH) + 1;
377 int day = calendar.get(Calendar.DATE);
378 int hour = calendar.get(Calendar.HOUR_OF_DAY);
379 int minute = calendar.get(Calendar.MINUTE);
380 int sec = calendar.get(Calendar.SECOND);
381 int msec = calendar.get(Calendar.MILLISECOND);
383 String attrVal = MessageFormat.format(
384 "{0,number,#0000}-{1,number,#00}-{2,number,#00}"
385 +"T{3,number,#00}:{4,number,#00}:{5,number,#00}"
386 +".{6,number,#000}+09:00",
387 year, month, day, hour, minute, sec, msec);
389 attrOut(writer, name, attrVal);
395 * デコードエラー情報をrawdataタグで出力する。
396 * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。
398 * @param errorInfo デコードエラー
399 * @throws IOException 出力エラー
401 public static void dumpErrorInfo(Writer writer,
402 DecodeErrorInfo errorInfo)
405 hexVal = errorInfo.getRawByte1st() & 0xff;
406 if(errorInfo.has2nd()){
408 hexVal |= errorInfo.getRawByte2nd() & 0xff;
411 String hexBin = Integer.toHexString(hexVal);
412 if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
414 char replaceChar = Win31j.getWin31jChar(errorInfo);
416 writer.append("<rawdata");
419 attrOut(writer, "encoding", "Shift_JIS");
422 attrOut(writer, "hexBin", hexBin);
425 writer.append(replaceChar);
426 writer.append("</rawdata>");
432 * デコードエラー込みのテキストを出力する。
434 * @param content テキスト
435 * @throws IOException 出力エラー
437 public static void dumpDecodedContent(Writer writer,
438 DecodedContent content)
440 if( ! content.hasDecodeError() ){
441 textOut(writer, content);
447 List<DecodeErrorInfo> errList = content.getDecodeErrorList();
448 for(DecodeErrorInfo err : errList){
449 int charPos = err.getCharPosition();
450 CharSequence line = content.subSequence(last, charPos);
451 textOut(writer, line);
452 dumpErrorInfo(writer, err);
456 CharSequence line = content.subSequence(last, content.length());
457 textOut(writer, line);
465 * @param villageData 村情報
466 * @throws IOException 出力エラー
468 public static void dumpVillageData(Writer writer,
469 VillageData villageData)
471 writer.append("<?xml");
473 attrOut(writer, "version", "1.0");
475 attrOut(writer, "encoding", "UTF-8");
476 writer.append(" ?>\n\n");
478 writer.append("<!--\n");
479 writer.append(" 人狼BBSアーカイブ\n");
480 writer.append(" http://jindolf.sourceforge.jp/\n");
481 writer.append("-->\n\n");
484 writer.append("\n\n");
486 villageData.dumpXml(writer);
488 writer.append("\n<!-- EOF -->\n");