-/*\r
- * XML utils\r
- *\r
- * License : The MIT License\r
- * Copyright(c) 2008 olyutorskii\r
- */\r
-\r
-package jp.sourceforge.jindolf.archiver;\r
-\r
-import java.io.BufferedOutputStream;\r
-import java.io.BufferedWriter;\r
-import java.io.File;\r
-import java.io.FileOutputStream;\r
-import java.io.IOException;\r
-import java.io.OutputStream;\r
-import java.io.OutputStreamWriter;\r
-import java.io.Writer;\r
-import java.text.MessageFormat;\r
-import java.util.Calendar;\r
-import java.util.GregorianCalendar;\r
-import java.util.List;\r
-import java.util.TimeZone;\r
-import jp.sourceforge.jindolf.corelib.LandDef;\r
-import jp.sourceforge.jindolf.parser.DecodeErrorInfo;\r
-import jp.sourceforge.jindolf.parser.DecodedContent;\r
-\r
-/**\r
- * XML用各種ユーティリティ。\r
- */\r
-public final class XmlUtils{\r
-\r
- private static final String ORIG_DTD =\r
- "http://jindolf.sourceforge.jp/xml/dtd/bbsArchive-110421.dtd";\r
- private static final String ORIG_NS =\r
- "http://jindolf.sourceforge.jp/xml/ns/501";\r
- private static final String ORIG_SCHEME =\r
- "http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-110421.xsd";\r
- private static final String SCHEMA_NS =\r
- "http://www.w3.org/2001/XMLSchema-instance";\r
-\r
- private static final String OUTPATH = "D:\\TEMP\\zxzx\\";\r
-\r
- private static final char BS_CHAR = (char) 0x005c; // Backslash\r
- private static final String INDENT_UNIT = "\u0020\u0020";\r
-\r
- private static final TimeZone TZ_TOKYO =\r
- TimeZone.getTimeZone("Asia/Tokyo");\r
-\r
-\r
- /**\r
- * 隠れコンストラクタ。\r
- */\r
- private XmlUtils(){\r
- throw new Error();\r
- }\r
-\r
-\r
- /**\r
- * DOCTYPE宣言を出力する。\r
- * @param writer 出力先\r
- * @throws IOException 出力エラー\r
- */\r
- public static void dumpDocType(Writer writer) throws IOException{\r
- writer.append("<!DOCTYPE village SYSTEM ");\r
- writer.append('"');\r
- writer.append(ORIG_DTD);\r
- writer.append('"');\r
- writer.append(" >");\r
- return;\r
- }\r
-\r
- /**\r
- * オリジナルNameSpace宣言を出力する。\r
- * @param writer 出力先\r
- * @throws IOException 出力エラー\r
- */\r
- public static void dumpNameSpaceDecl(Writer writer)\r
- throws IOException{\r
- attrOut(writer, "xmlns", ORIG_NS);\r
- return;\r
- }\r
-\r
- /**\r
- * スキーマNameSpace宣言を出力する。\r
- * @param writer 出力先\r
- * @throws IOException 出力エラー\r
- */\r
- public static void dumpSiNameSpaceDecl(Writer writer)\r
- throws IOException{\r
- attrOut(writer, "xmlns:xsi", SCHEMA_NS);\r
- return;\r
- }\r
-\r
- /**\r
- * スキーマ位置指定を出力する。\r
- * @param writer 出力先\r
- * @throws IOException 出力エラー\r
- */\r
- public static void dumpSchemeLocation(Writer writer)\r
- throws IOException{\r
- attrOut(writer,\r
- "xsi:schemaLocation",\r
- ORIG_NS + " " + ORIG_SCHEME);\r
- return;\r
- }\r
-\r
- /**\r
- * インデント用空白を出力する。\r
- * ネスト単位は空白2文字\r
- * @param writer 出力先\r
- * @param level ネストレベル\r
- * @throws IOException 出力エラー\r
- */\r
- public static void indent(Writer writer, int level) throws IOException{\r
- for(int ct = 1; ct <= level; ct++){\r
- writer.append(INDENT_UNIT);\r
- }\r
- return;\r
- }\r
-\r
- /**\r
- * XML数値文字参照を出力する。\r
- * @param writer 出力先\r
- * @param chVal 出力文字\r
- * @throws IOException 出力エラー\r
- */\r
- public static void charRefOut(Writer writer, char chVal)\r
- throws IOException{\r
- if(chVal == '\u0020'){\r
- writer.append(" ");\r
- return;\r
- }\r
-\r
- if(chVal == '\u0009'){\r
- writer.append("	");\r
- return;\r
- }\r
-\r
- int ival = 0xffff & ((int) chVal);\r
- String hex = Integer.toHexString(ival);\r
- if(hex.length() % 2 != 0) hex = "0" + hex;\r
-\r
- writer.append("&#x");\r
- writer.append(hex);\r
- writer.append(";");\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * 不正文字をXML出力する。\r
- * @param writer 出力先\r
- * @param chVal 不正文字\r
- * @throws IOException 出力エラー\r
- */\r
- public static void dumpInvalidChar(Writer writer, char chVal)\r
- throws IOException{\r
- int hexVal;\r
- hexVal = chVal & 0xff;\r
- String hexBin = Integer.toHexString(hexVal);\r
- if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;\r
-\r
- char replaceChar = '\ufffd';\r
- if('\u0000' <= chVal && chVal <= '\u001f'){\r
- replaceChar = (char)( chVal + '\u2400' );\r
- }\r
-\r
- writer.append("<rawdata");\r
-\r
- writer.append(' ');\r
- attrOut(writer, "encoding", "Shift_JIS");\r
-\r
- writer.append(' ');\r
- attrOut(writer, "hexBin", hexBin);\r
-\r
- writer.append(" >");\r
- writer.append(replaceChar);\r
- writer.append("</rawdata>");\r
- }\r
-\r
- /**\r
- * 任意の文字がXML規格上のホワイトスペースに属するか判定する。\r
- * @param chVal 文字\r
- * @return ホワイトスペースならtrue\r
- */\r
- public static boolean isWhiteSpace(char chVal){\r
- switch(chVal){\r
- case '\u0020':\r
- case '\t':\r
- case '\n':\r
- case '\r':\r
- return true;\r
- default:\r
- break;\r
- }\r
-\r
- return false;\r
- }\r
-\r
- /**\r
- * 文字列を出力する。\r
- * <ul>\r
- * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。\r
- * <li>連続したホワイトスペースの2文字目以降は文字参照化される。\r
- * <li>スペースでないホワイトスペースは無条件に文字参照化される。\r
- * <li>{@literal &, <, >, "}は無条件に文字参照化される。\r
- * </ul>\r
- * 参考:XML 1.0 規格 3.3.3節\r
- * @param writer 出力先\r
- * @param seq CDATA文字列\r
- * @throws IOException 出力エラー\r
- */\r
- public static void textOut(Writer writer, CharSequence seq)\r
- throws IOException{\r
- int len = seq.length();\r
-\r
- boolean leadSpace = false;\r
-\r
- for(int pos = 0; pos < len; pos++){\r
- char chVal = seq.charAt(pos);\r
-\r
- if(isWhiteSpace(chVal)){\r
- if(pos == 0 || pos >= len - 1 || leadSpace){\r
- charRefOut(writer, chVal);\r
- }else if(chVal != '\u0020'){\r
- charRefOut(writer, chVal);\r
- }else{\r
- writer.append(chVal);\r
- }\r
- leadSpace = true;\r
- }else{\r
- if(chVal == '&'){\r
- writer.append("&");\r
- }else if(chVal == '<'){\r
- writer.append("<");\r
- }else if(chVal == '>'){\r
- writer.append(">");\r
- }else if(chVal == '"'){\r
- writer.append(""");\r
- }else if(chVal == '\''){\r
- writer.append("'");\r
- }else if(chVal == BS_CHAR){\r
- writer.append('\u00a5');\r
- }else if(chVal == '\u007e'){\r
- writer.append('\u203e');\r
- }else if(Character.isISOControl(chVal)){\r
- dumpInvalidChar(writer, chVal);\r
- }else{\r
- writer.append(chVal);\r
- }\r
- leadSpace = false;\r
- }\r
- }\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * 属性を出力する。\r
- * @param writer 出力先\r
- * @param name 属性名\r
- * @param value 属性値\r
- * @throws IOException 出力エラー\r
- */\r
- public static void attrOut(Writer writer,\r
- CharSequence name,\r
- CharSequence value)\r
- throws IOException{\r
- StringBuilder newValue = new StringBuilder(value);\r
- for(int pt = 0; pt < newValue.length(); pt++){\r
- char chVal = newValue.charAt(pt);\r
- if(chVal == '\n' || chVal == '\r' || chVal == '\t') continue;\r
- if(Character.isISOControl(chVal)){\r
- newValue.setCharAt(pt, (char)('\u2400' + chVal));\r
- }\r
- }\r
-\r
- writer.append(name);\r
- writer.append('=');\r
- writer.append('"');\r
- textOut(writer, newValue);\r
- writer.append('"');\r
- return;\r
- }\r
-\r
- /**\r
- * xsd:time形式の時刻属性を出力する。\r
- * タイムゾーンは「+09:00」固定\r
- * @param writer 出力先\r
- * @param name 属性名\r
- * @param hour 時間\r
- * @param minute 分\r
- * @throws IOException 出力エラー\r
- */\r
- public static void timeAttrOut(Writer writer,\r
- CharSequence name,\r
- int hour, int minute)\r
- throws IOException{\r
- String cmtTime =\r
- MessageFormat\r
- .format("{0,number,#00}:{1,number,#00}:00+09:00",\r
- hour, minute);\r
- attrOut(writer, name, cmtTime);\r
- return;\r
- }\r
-\r
- /**\r
- * xsd:gMonthDay形式の日付属性を出力する。\r
- * タイムゾーンは「+09:00」固定\r
- * @param writer 出力先\r
- * @param name 属性名\r
- * @param month 月\r
- * @param day 日\r
- * @throws IOException 出力エラー\r
- */\r
- public static void dateAttrOut(Writer writer,\r
- CharSequence name,\r
- int month, int day)\r
- throws IOException{\r
- String dateAttr =\r
- MessageFormat.format("--{0,number,#00}-{1,number,#00}+09:00",\r
- month, day);\r
- attrOut(writer, name, dateAttr);\r
- return;\r
- }\r
-\r
- /**\r
- * xsd:dateTime形式の日付時刻属性を出力する。\r
- * タイムゾーンは「+09:00」固定\r
- * @param writer 出力先\r
- * @param name 属性名\r
- * @param epochMs エポック時刻\r
- * @throws IOException 出力エラー\r
- */\r
- public static void dateTimeAttr(Writer writer,\r
- CharSequence name,\r
- long epochMs)\r
- throws IOException{\r
- Calendar calendar = new GregorianCalendar(TZ_TOKYO);\r
-\r
- calendar.setTimeInMillis(epochMs);\r
- int year = calendar.get(Calendar.YEAR);\r
- int month = calendar.get(Calendar.MONTH) + 1;\r
- int day = calendar.get(Calendar.DATE);\r
- int hour = calendar.get(Calendar.HOUR_OF_DAY);\r
- int minute = calendar.get(Calendar.MINUTE);\r
- int sec = calendar.get(Calendar.SECOND);\r
- int msec = calendar.get(Calendar.MILLISECOND);\r
-\r
- String attrVal = MessageFormat.format(\r
- "{0,number,#0000}-{1,number,#00}-{2,number,#00}"\r
- +"T{3,number,#00}:{4,number,#00}:{5,number,#00}"\r
- +".{6,number,#000}+09:00",\r
- year, month, day, hour, minute, sec, msec);\r
-\r
- attrOut(writer, name, attrVal);\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * デコードエラー情報をrawdataタグで出力する。\r
- * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。\r
- * @param writer 出力先\r
- * @param errorInfo デコードエラー\r
- * @throws IOException 出力エラー\r
- */\r
- public static void dumpErrorInfo(Writer writer,\r
- DecodeErrorInfo errorInfo)\r
- throws IOException{\r
- int hexVal;\r
- hexVal = errorInfo.getRawByte1st() & 0xff;\r
- if(errorInfo.has2nd()){\r
- hexVal <<= 8;\r
- hexVal |= errorInfo.getRawByte2nd() & 0xff;\r
- }\r
-\r
- String hexBin = Integer.toHexString(hexVal);\r
- if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;\r
-\r
- char replaceChar = Win31j.getWin31jChar(errorInfo);\r
-\r
- writer.append("<rawdata");\r
-\r
- writer.append(' ');\r
- attrOut(writer, "encoding", "Shift_JIS");\r
-\r
- writer.append(' ');\r
- attrOut(writer, "hexBin", hexBin);\r
-\r
- writer.append(" >");\r
- writer.append(replaceChar);\r
- writer.append("</rawdata>");\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * デコードエラー込みのテキストを出力する。\r
- * @param writer 出力先\r
- * @param content テキスト\r
- * @throws IOException 出力エラー\r
- */\r
- public static void dumpDecodedContent(Writer writer,\r
- DecodedContent content)\r
- throws IOException{\r
- if( ! content.hasDecodeError() ){\r
- textOut(writer, content);\r
- return;\r
- }\r
-\r
- int last = 0;\r
-\r
- List<DecodeErrorInfo> errList = content.getDecodeErrorList();\r
- for(DecodeErrorInfo err : errList){\r
- int charPos = err.getCharPosition();\r
- CharSequence line = content.subSequence(last, charPos);\r
- textOut(writer, line);\r
- dumpErrorInfo(writer, err);\r
- last = charPos + 1;\r
- }\r
-\r
- CharSequence line = content.subSequence(last, content.length());\r
- textOut(writer, line);\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * 村情報をXML形式で出力する。\r
- * @param writer 出力先\r
- * @param villageData 村情報\r
- * @throws IOException 出力エラー\r
- */\r
- public static void dumpVillageData(Writer writer,\r
- VillageData villageData)\r
- throws IOException{\r
- writer.append("<?xml");\r
- writer.append(' ');\r
- attrOut(writer, "version", "1.0");\r
- writer.append(' ');\r
- attrOut(writer, "encoding", "UTF-8");\r
- writer.append(" ?>\n\n");\r
-\r
- writer.append("<!--\n");\r
- writer.append(" 人狼BBSアーカイブ\n");\r
- writer.append(" http://jindolf.sourceforge.jp/\n");\r
- writer.append("-->\n\n");\r
-\r
- dumpDocType(writer);\r
- writer.append("\n\n");\r
-\r
- villageData.dumpXml(writer);\r
-\r
- writer.append("\n<!-- EOF -->\n");\r
-\r
- writer.flush();\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * 村情報を反映した出力ファイル名を生成する。\r
- * @param village 村情報\r
- * @return XML出力ファイル名\r
- */\r
- public static String createOutFileName(VillageData village){\r
- LandDef landDef = village.getLandDef();\r
- String landId = landDef.getLandId();\r
- int vid = village.getVillageId();\r
-\r
- String fname =\r
- MessageFormat.format(\r
- "{0}jin_{1}_{2,number,#00000}.xml", OUTPATH, landId, vid);\r
- return fname;\r
- }\r
-\r
- /**\r
- * 村情報を反映した出力ファイルへの文字ストリームを生成する。\r
- * @param village 村情報\r
- * @return 出力先文字ストリーム\r
- * @throws IOException 出力エラー\r
- */\r
- public static Writer createFileWriter(VillageData village)\r
- throws IOException{\r
- String fname = createOutFileName(village);\r
- File file = new File(fname);\r
-\r
- OutputStream ostream;\r
- ostream = new FileOutputStream(file);\r
- ostream = new BufferedOutputStream(ostream, 10000);\r
- Writer writer;\r
- writer = new OutputStreamWriter(ostream, "UTF-8");\r
- writer = new BufferedWriter(writer, 10000);\r
- return writer;\r
- }\r
-\r
-}\r
+/*
+ * XML utils
+ *
+ * License : The MIT License
+ * Copyright(c) 2008 olyutorskii
+ */
+
+package jp.sourceforge.jindolf.archiver;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.text.MessageFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.TimeZone;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+import jp.sourceforge.jindolf.parser.DecodeErrorInfo;
+import jp.sourceforge.jindolf.parser.DecodedContent;
+import org.xml.sax.SAXException;
+
+/**
+ * XML用各種ユーティリティ。
+ */
+public final class XmlUtils{
+
+ private static final String ORIG_DTD =
+ "http://jindolf.sourceforge.jp/xml/dtd/bbsArchive-110421.dtd";
+ private static final String ORIG_NS =
+ "http://jindolf.sourceforge.jp/xml/ns/501";
+ private static final String ORIG_SCHEME =
+ "http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-110421.xsd";
+ private static final String SCHEMA_NS =
+ "http://www.w3.org/2001/XMLSchema-instance";
+
+ private static final char BS_CHAR = (char) 0x005c; // Backslash
+ private static final String INDENT_UNIT = "\u0020\u0020";
+
+ private static final TimeZone TZ_TOKYO =
+ TimeZone.getTimeZone("Asia/Tokyo");
+
+
+ /**
+ * 隠れコンストラクタ。
+ */
+ private XmlUtils(){
+ throw new Error();
+ }
+
+
+ /**
+ * XML読み込み用DocumentBuilderを生成する。
+ * @return DocumentBuilder
+ * @throws ParserConfigurationException 実装が要求に応えられない。
+ */
+ public static DocumentBuilder createDocumentBuilder()
+ throws ParserConfigurationException {
+ DocumentBuilderFactory factory;
+ factory = DocumentBuilderFactory.newInstance();
+
+ DocumentBuilder builder;
+ builder = factory.newDocumentBuilder();
+
+ return builder;
+ }
+
+ /**
+ * バリデータを生成する。
+ * @return バリデータ
+ * @throws SAXException 実装が要求に応えられない。
+ */
+ public static Validator createValidator() throws SAXException{
+ SchemaFactory factory;
+ String nsuri = XMLConstants.W3C_XML_SCHEMA_NS_URI;
+ factory = SchemaFactory.newInstance(nsuri);
+
+ Schema schema;
+ schema = factory.newSchema();
+
+ Validator validator = schema.newValidator();
+
+ return validator;
+ }
+
+ /**
+ * DOCTYPE宣言を出力する。
+ * @param writer 出力先
+ * @throws IOException 出力エラー
+ */
+ public static void dumpDocType(Writer writer) throws IOException{
+ writer.append("<!DOCTYPE village SYSTEM ");
+ writer.append('"');
+ writer.append(ORIG_DTD);
+ writer.append('"');
+ writer.append(" >");
+ return;
+ }
+
+ /**
+ * オリジナルNameSpace宣言を出力する。
+ * @param writer 出力先
+ * @throws IOException 出力エラー
+ */
+ public static void dumpNameSpaceDecl(Writer writer)
+ throws IOException{
+ attrOut(writer, "xmlns", ORIG_NS);
+ return;
+ }
+
+ /**
+ * スキーマNameSpace宣言を出力する。
+ * @param writer 出力先
+ * @throws IOException 出力エラー
+ */
+ public static void dumpSiNameSpaceDecl(Writer writer)
+ throws IOException{
+ attrOut(writer, "xmlns:xsi", SCHEMA_NS);
+ return;
+ }
+
+ /**
+ * スキーマ位置指定を出力する。
+ * @param writer 出力先
+ * @throws IOException 出力エラー
+ */
+ public static void dumpSchemeLocation(Writer writer)
+ throws IOException{
+ attrOut(writer,
+ "xsi:schemaLocation",
+ ORIG_NS + " " + ORIG_SCHEME);
+ return;
+ }
+
+ /**
+ * インデント用空白を出力する。
+ * ネスト単位は空白2文字
+ * @param writer 出力先
+ * @param level ネストレベル
+ * @throws IOException 出力エラー
+ */
+ public static void indent(Writer writer, int level) throws IOException{
+ for(int ct = 1; ct <= level; ct++){
+ writer.append(INDENT_UNIT);
+ }
+ return;
+ }
+
+ /**
+ * XML数値文字参照を出力する。
+ * @param writer 出力先
+ * @param chVal 出力文字
+ * @throws IOException 出力エラー
+ */
+ public static void charRefOut(Writer writer, char chVal)
+ throws IOException{
+ if(chVal == '\u0020'){
+ writer.append(" ");
+ return;
+ }
+
+ if(chVal == '\u0009'){
+ writer.append("	");
+ return;
+ }
+
+ int ival = 0xffff & ((int) chVal);
+ String hex = Integer.toHexString(ival);
+ if(hex.length() % 2 != 0) hex = "0" + hex;
+
+ writer.append("&#x");
+ writer.append(hex);
+ writer.append(";");
+
+ return;
+ }
+
+ /**
+ * 不正文字をXML出力する。
+ * @param writer 出力先
+ * @param chVal 不正文字
+ * @throws IOException 出力エラー
+ */
+ public static void dumpInvalidChar(Writer writer, char chVal)
+ throws IOException{
+ int hexVal;
+ hexVal = chVal & 0xff;
+ String hexBin = Integer.toHexString(hexVal);
+ if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
+
+ char replaceChar = '\ufffd';
+ if('\u0000' <= chVal && chVal <= '\u001f'){
+ replaceChar = (char)( chVal + '\u2400' );
+ }
+
+ writer.append("<rawdata");
+
+ writer.append(' ');
+ attrOut(writer, "encoding", "Shift_JIS");
+
+ writer.append(' ');
+ attrOut(writer, "hexBin", hexBin);
+
+ writer.append(" >");
+ writer.append(replaceChar);
+ writer.append("</rawdata>");
+ }
+
+ /**
+ * 任意の文字がXML規格上のホワイトスペースに属するか判定する。
+ * @param chVal 文字
+ * @return ホワイトスペースならtrue
+ */
+ public static boolean isWhiteSpace(char chVal){
+ switch(chVal){
+ case '\u0020':
+ case '\t':
+ case '\n':
+ case '\r':
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+ }
+
+ /**
+ * 文字列を出力する。
+ * <ul>
+ * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。
+ * <li>連続したホワイトスペースの2文字目以降は文字参照化される。
+ * <li>スペースでないホワイトスペースは無条件に文字参照化される。
+ * <li>{@literal &, <, >, "}は無条件に文字参照化される。
+ * </ul>
+ * 参考:XML 1.0 規格 3.3.3節
+ * @param writer 出力先
+ * @param seq CDATA文字列
+ * @throws IOException 出力エラー
+ */
+ public static void textOut(Writer writer, CharSequence seq)
+ throws IOException{
+ int len = seq.length();
+
+ boolean leadSpace = false;
+
+ for(int pos = 0; pos < len; pos++){
+ char chVal = seq.charAt(pos);
+
+ if(isWhiteSpace(chVal)){
+ if(pos == 0 || pos >= len - 1 || leadSpace){
+ charRefOut(writer, chVal);
+ }else if(chVal != '\u0020'){
+ charRefOut(writer, chVal);
+ }else{
+ writer.append(chVal);
+ }
+ leadSpace = true;
+ }else{
+ if(chVal == '&'){
+ writer.append("&");
+ }else if(chVal == '<'){
+ writer.append("<");
+ }else if(chVal == '>'){
+ writer.append(">");
+ }else if(chVal == '"'){
+ writer.append(""");
+ }else if(chVal == '\''){
+ writer.append("'");
+ }else if(chVal == BS_CHAR){
+ writer.append('\u00a5');
+ }else if(chVal == '\u007e'){
+ writer.append('\u203e');
+ }else if(Character.isISOControl(chVal)){
+ dumpInvalidChar(writer, chVal);
+ }else{
+ writer.append(chVal);
+ }
+ leadSpace = false;
+ }
+ }
+
+ return;
+ }
+
+ /**
+ * 属性を出力する。
+ * @param writer 出力先
+ * @param name 属性名
+ * @param value 属性値
+ * @throws IOException 出力エラー
+ */
+ public static void attrOut(Writer writer,
+ CharSequence name,
+ CharSequence value)
+ throws IOException{
+ StringBuilder newValue = new StringBuilder(value);
+ for(int pt = 0; pt < newValue.length(); pt++){
+ char chVal = newValue.charAt(pt);
+ if(chVal == '\n' || chVal == '\r' || chVal == '\t') continue;
+ if(Character.isISOControl(chVal)){
+ newValue.setCharAt(pt, (char)('\u2400' + chVal));
+ }
+ }
+
+ writer.append(name);
+ writer.append('=');
+ writer.append('"');
+ textOut(writer, newValue);
+ writer.append('"');
+ return;
+ }
+
+ /**
+ * xsd:time形式の時刻属性を出力する。
+ * タイムゾーンは「+09:00」固定
+ * @param writer 出力先
+ * @param name 属性名
+ * @param hour 時間
+ * @param minute 分
+ * @throws IOException 出力エラー
+ */
+ public static void timeAttrOut(Writer writer,
+ CharSequence name,
+ int hour, int minute)
+ throws IOException{
+ String cmtTime =
+ MessageFormat
+ .format("{0,number,#00}:{1,number,#00}:00+09:00",
+ hour, minute);
+ attrOut(writer, name, cmtTime);
+ return;
+ }
+
+ /**
+ * xsd:gMonthDay形式の日付属性を出力する。
+ * タイムゾーンは「+09:00」固定
+ * @param writer 出力先
+ * @param name 属性名
+ * @param month 月
+ * @param day 日
+ * @throws IOException 出力エラー
+ */
+ public static void dateAttrOut(Writer writer,
+ CharSequence name,
+ int month, int day)
+ throws IOException{
+ String dateAttr =
+ MessageFormat.format("--{0,number,#00}-{1,number,#00}+09:00",
+ month, day);
+ attrOut(writer, name, dateAttr);
+ return;
+ }
+
+ /**
+ * xsd:dateTime形式の日付時刻属性を出力する。
+ * タイムゾーンは「+09:00」固定
+ * @param writer 出力先
+ * @param name 属性名
+ * @param epochMs エポック時刻
+ * @throws IOException 出力エラー
+ */
+ public static void dateTimeAttr(Writer writer,
+ CharSequence name,
+ long epochMs)
+ throws IOException{
+ Calendar calendar = new GregorianCalendar(TZ_TOKYO);
+
+ calendar.setTimeInMillis(epochMs);
+ int year = calendar.get(Calendar.YEAR);
+ int month = calendar.get(Calendar.MONTH) + 1;
+ int day = calendar.get(Calendar.DATE);
+ int hour = calendar.get(Calendar.HOUR_OF_DAY);
+ int minute = calendar.get(Calendar.MINUTE);
+ int sec = calendar.get(Calendar.SECOND);
+ int msec = calendar.get(Calendar.MILLISECOND);
+
+ String attrVal = MessageFormat.format(
+ "{0,number,#0000}-{1,number,#00}-{2,number,#00}"
+ +"T{3,number,#00}:{4,number,#00}:{5,number,#00}"
+ +".{6,number,#000}+09:00",
+ year, month, day, hour, minute, sec, msec);
+
+ attrOut(writer, name, attrVal);
+
+ return;
+ }
+
+ /**
+ * デコードエラー情報をrawdataタグで出力する。
+ * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。
+ * @param writer 出力先
+ * @param errorInfo デコードエラー
+ * @throws IOException 出力エラー
+ */
+ public static void dumpErrorInfo(Writer writer,
+ DecodeErrorInfo errorInfo)
+ throws IOException{
+ int hexVal;
+ hexVal = errorInfo.getRawByte1st() & 0xff;
+ if(errorInfo.has2nd()){
+ hexVal <<= 8;
+ hexVal |= errorInfo.getRawByte2nd() & 0xff;
+ }
+
+ String hexBin = Integer.toHexString(hexVal);
+ if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
+
+ char replaceChar = Win31j.getWin31jChar(errorInfo);
+
+ writer.append("<rawdata");
+
+ writer.append(' ');
+ attrOut(writer, "encoding", "Shift_JIS");
+
+ writer.append(' ');
+ attrOut(writer, "hexBin", hexBin);
+
+ writer.append(" >");
+ writer.append(replaceChar);
+ writer.append("</rawdata>");
+
+ return;
+ }
+
+ /**
+ * デコードエラー込みのテキストを出力する。
+ * @param writer 出力先
+ * @param content テキスト
+ * @throws IOException 出力エラー
+ */
+ public static void dumpDecodedContent(Writer writer,
+ DecodedContent content)
+ throws IOException{
+ if( ! content.hasDecodeError() ){
+ textOut(writer, content);
+ return;
+ }
+
+ int last = 0;
+
+ List<DecodeErrorInfo> errList = content.getDecodeErrorList();
+ for(DecodeErrorInfo err : errList){
+ int charPos = err.getCharPosition();
+ CharSequence line = content.subSequence(last, charPos);
+ textOut(writer, line);
+ dumpErrorInfo(writer, err);
+ last = charPos + 1;
+ }
+
+ CharSequence line = content.subSequence(last, content.length());
+ textOut(writer, line);
+
+ return;
+ }
+
+ /**
+ * 村情報をXML形式で出力する。
+ * @param writer 出力先
+ * @param villageData 村情報
+ * @throws IOException 出力エラー
+ */
+ public static void dumpVillageData(Writer writer,
+ VillageData villageData)
+ throws IOException{
+ writer.append("<?xml");
+ writer.append(' ');
+ attrOut(writer, "version", "1.0");
+ writer.append(' ');
+ attrOut(writer, "encoding", "UTF-8");
+ writer.append(" ?>\n\n");
+
+ writer.append("<!--\n");
+ writer.append(" 人狼BBSアーカイブ\n");
+ writer.append(" http://jindolf.sourceforge.jp/\n");
+ writer.append("-->\n\n");
+
+ dumpDocType(writer);
+ writer.append("\n\n");
+
+ villageData.dumpXml(writer);
+
+ writer.append("\n<!-- EOF -->\n");
+
+ writer.flush();
+
+ return;
+ }
+
+}