OSDN Git Service

checkstyleのバグ回避
[jindolf/JinArchiver.git] / src / main / java / jp / sourceforge / jindolf / archiver / XmlUtils.java
1 /*\r
2  * XML utils\r
3  *\r
4  * License : The MIT License\r
5  * Copyright(c) 2008 olyutorskii\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf.archiver;\r
9 \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
26 \r
27 /**\r
28  * XML用各種ユーティリティ\r
29  */\r
30 public final class XmlUtils{\r
31 \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
40 \r
41     public static final String OUTPATH = "D:\\TEMP\\zxzx\\";\r
42 \r
43     private static final char BS_CHAR = (char) 0x005c; // Backslash\r
44     private static final String INDENT_UNIT = "\u0020\u0020";\r
45 \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
50 \r
51 \r
52     /**\r
53      * 隠れコンストラクタ\r
54      */\r
55     private XmlUtils(){\r
56         throw new Error();\r
57     }\r
58 \r
59 \r
60     /**\r
61      * DOCTYPE宣言を出力する。\r
62      * @param writer 出力先\r
63      * @throws IOException 出力エラー\r
64      */\r
65     public static void dumpDocType(Writer writer) throws IOException{\r
66         writer.append("<!DOCTYPE village SYSTEM ");\r
67         writer.append('"');\r
68         writer.append(ORIG_DTD);\r
69         writer.append('"');\r
70         writer.append(" >");\r
71         return;\r
72     }\r
73 \r
74     /**\r
75      * オリジナルNameSpace宣言を出力する。\r
76      * @param writer 出力先\r
77      * @throws IOException 出力エラー\r
78      */\r
79     public static void dumpNameSpaceDecl(Writer writer)\r
80             throws IOException{\r
81         attrOut(writer, "xmlns", ORIG_NS);\r
82         return;\r
83     }\r
84 \r
85     /**\r
86      * スキーマNameSpace宣言を出力する。\r
87      * @param writer 出力先\r
88      * @throws IOException 出力エラー\r
89      */\r
90     public static void dumpSiNameSpaceDecl(Writer writer)\r
91             throws IOException{\r
92         attrOut(writer, "xmlns:xsi", SCHEMA_NS);\r
93         return;\r
94     }\r
95 \r
96     /**\r
97      * スキーマ位置指定を出力する。\r
98      * @param writer 出力先\r
99      * @throws IOException 出力エラー\r
100      */\r
101     public static void dumpSchemeLocation(Writer writer)\r
102             throws IOException{\r
103         attrOut(writer,\r
104                 "xsi:schemaLocation",\r
105                 ORIG_NS + " " + ORIG_SCHEME);\r
106         return;\r
107     }\r
108 \r
109     /**\r
110      * インデント用空白を出力する。\r
111      * ネスト単位は空白2文字\r
112      * @param writer 出力先\r
113      * @param level ネストレベル\r
114      * @throws IOException 出力エラー\r
115      */\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
119         }\r
120         return;\r
121     }\r
122 \r
123     /**\r
124      * XML数値文字参照を出力する。\r
125      * @param writer 出力先\r
126      * @param chVal 出力文字\r
127      * @throws IOException 出力エラー\r
128      */\r
129     public static void charRefOut(Writer writer, char chVal)\r
130             throws IOException{\r
131         if(chVal == '\u0020'){\r
132             writer.append("&#x20;");\r
133             return;\r
134         }\r
135 \r
136         if(chVal == '\u0009'){\r
137             writer.append("&#x09;");\r
138             return;\r
139         }\r
140 \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
144 \r
145         writer.append("&#x");\r
146         writer.append(hex);\r
147         writer.append(";");\r
148 \r
149         return;\r
150     }\r
151 \r
152     /**\r
153      * 不正文字をXML出力する。\r
154      * @param writer 出力先\r
155      * @param chVal 不正文字\r
156      * @throws IOException 出力エラー\r
157      */\r
158     public static void dumpInvalidChar(Writer writer, char chVal)\r
159             throws IOException{\r
160         int hexVal;\r
161         hexVal = chVal & 0xff;\r
162         String hexBin = Integer.toHexString(hexVal);\r
163         if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;\r
164 \r
165         char replaceChar = '\ufffd';\r
166         if('\u0000' <= chVal && chVal <= '\u001f'){\r
167             replaceChar = (char)( chVal + '\u2400' );\r
168         }\r
169 \r
170         writer.append("<rawdata");\r
171 \r
172         writer.append(' ');\r
173         attrOut(writer, "encoding", "Shift_JIS");\r
174 \r
175         writer.append(' ');\r
176         attrOut(writer, "hexBin", hexBin);\r
177 \r
178         writer.append(" >");\r
179         writer.append(replaceChar);\r
180         writer.append("</rawdata>");\r
181     }\r
182 \r
183     /**\r
184      * 任意の文字がXML規格上のホワイトスペースに属するか判定する。\r
185      * @param chVal 文字\r
186      * @return ホワイトスペースならtrue\r
187      */\r
188     public static boolean isWhiteSpace(char chVal){\r
189         switch(chVal){\r
190         case '\u0020':\r
191         case '\t':\r
192         case '\n':\r
193         case '\r':\r
194             return true;\r
195         default:\r
196             break;\r
197         }\r
198 \r
199         return false;\r
200     }\r
201 \r
202     /**\r
203      * 文字列を出力する。\r
204      * <ul>\r
205      * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。\r
206      * <li>連続したホワイトスペースの2文字目以降は文字参照化される。\r
207      * <li>スペースでないホワイトスペースは無条件に文字参照化される。\r
208      * <li>{@literal &, <, >, "}は無条件に文字参照化される。\r
209      * </ul>\r
210      * 参考:XML 1.0 規格 3.3.3節\r
211      * @param writer 出力先\r
212      * @param seq CDATA文字列\r
213      * @throws IOException 出力エラー\r
214      */\r
215     public static void textOut(Writer writer, CharSequence seq)\r
216             throws IOException{\r
217         int len = seq.length();\r
218 \r
219         boolean leadSpace = false;\r
220 \r
221         for(int pos = 0; pos < len; pos++){\r
222             char chVal = seq.charAt(pos);\r
223 \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
229                 }else{\r
230                     writer.append(chVal);\r
231                 }\r
232                 leadSpace = true;\r
233             }else{\r
234                 if(chVal == '&'){\r
235                     writer.append("&amp;");\r
236                 }else if(chVal == '<'){\r
237                     writer.append("&lt;");\r
238                 }else if(chVal == '>'){\r
239                     writer.append("&gt;");\r
240                 }else if(chVal == '"'){\r
241                     writer.append("&quot;");\r
242                 }else if(chVal == '\''){\r
243                     writer.append("&apos;");\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
250                 }else{\r
251                     writer.append(chVal);\r
252                 }\r
253                 leadSpace = false;\r
254             }\r
255         }\r
256 \r
257         return;\r
258     }\r
259 \r
260     /**\r
261      * 属性を出力する。\r
262      * @param writer 出力先\r
263      * @param name 属性名\r
264      * @param value 属性値\r
265      * @throws IOException 出力エラー\r
266      */\r
267     public static void attrOut(Writer writer,\r
268                                 CharSequence name,\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
277             }\r
278         }\r
279 \r
280         writer.append(name);\r
281         writer.append('=');\r
282         writer.append('"');\r
283         textOut(writer, newValue);\r
284         writer.append('"');\r
285         return;\r
286     }\r
287 \r
288     /**\r
289      * xsd:time形式の時刻属性を出力する。\r
290      * タイムゾーンは「+09:00」固定\r
291      * @param writer 出力先\r
292      * @param name 属性名\r
293      * @param hour 時間\r
294      * @param minute 分\r
295      * @throws IOException 出力エラー\r
296      */\r
297     public static void timeAttrOut(Writer writer,\r
298                                      CharSequence name,\r
299                                      int hour, int minute)\r
300             throws IOException{\r
301         String cmtTime =\r
302                 MessageFormat\r
303                 .format("{0,number,#00}:{1,number,#00}:00+09:00",\r
304                         hour, minute);\r
305         attrOut(writer, name, cmtTime);\r
306         return;\r
307     }\r
308 \r
309     /**\r
310      * xsd:gMonthDay形式の日付属性を出力する。\r
311      * タイムゾーンは「+09:00」固定\r
312      * @param writer 出力先\r
313      * @param name 属性名\r
314      * @param month 月\r
315      * @param day 日\r
316      * @throws IOException 出力エラー\r
317      */\r
318     public static void dateAttrOut(Writer writer,\r
319                                      CharSequence name,\r
320                                      int month, int day)\r
321             throws IOException{\r
322         String dateAttr =\r
323                 MessageFormat.format("--{0,number,#00}-{1,number,#00}+09:00",\r
324                                      month, day);\r
325         attrOut(writer, name, dateAttr);\r
326         return;\r
327     }\r
328 \r
329     /**\r
330      * xsd:dateTime形式の日付時刻属性を出力する。\r
331      * タイムゾーンは「+09:00」固定\r
332      * @param writer 出力先\r
333      * @param name 属性名\r
334      * @param epochMs エポック時刻\r
335      * @throws IOException 出力エラー\r
336      */\r
337     public static void dateTimeAttr(Writer writer,\r
338                                       CharSequence name,\r
339                                       long epochMs)\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
350 \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
356 \r
357             attrOut(writer, name, attrVal);\r
358         }\r
359         return;\r
360     }\r
361 \r
362     /**\r
363      * デコードエラー情報をrawdataタグで出力する。\r
364      * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。\r
365      * @param writer 出力先\r
366      * @param errorInfo デコードエラー\r
367      * @throws IOException 出力エラー\r
368      */\r
369     public static void dumpErrorInfo(Writer writer,\r
370                                        DecodeErrorInfo errorInfo)\r
371             throws IOException{\r
372         int hexVal;\r
373         hexVal = errorInfo.getRawByte1st() & 0xff;\r
374         if(errorInfo.has2nd()){\r
375             hexVal <<= 8;\r
376             hexVal |= errorInfo.getRawByte2nd() & 0xff;\r
377         }\r
378 \r
379         String hexBin = Integer.toHexString(hexVal);\r
380         if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;\r
381 \r
382         char replaceChar = Win31j.getWin31jChar(errorInfo);\r
383 \r
384         writer.append("<rawdata");\r
385 \r
386         writer.append(' ');\r
387         attrOut(writer, "encoding", "Shift_JIS");\r
388 \r
389         writer.append(' ');\r
390         attrOut(writer, "hexBin", hexBin);\r
391 \r
392         writer.append(" >");\r
393         writer.append(replaceChar);\r
394         writer.append("</rawdata>");\r
395 \r
396         return;\r
397     }\r
398 \r
399     /**\r
400      * デコードエラー込みのテキストを出力する。\r
401      * @param writer 出力先\r
402      * @param content テキスト\r
403      * @throws IOException 出力エラー\r
404      */\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
410             return;\r
411         }\r
412 \r
413         int last = 0;\r
414 \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
422         }\r
423 \r
424         CharSequence line = content.subSequence(last, content.length());\r
425         textOut(writer, line);\r
426 \r
427         return;\r
428     }\r
429 \r
430     /**\r
431      * 村情報をXML形式で出力する。\r
432      * @param writer 出力先\r
433      * @param villageData 村情報\r
434      * @throws IOException 出力エラー\r
435      */\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
445 \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
450 \r
451         dumpDocType(writer);\r
452         writer.append("\n\n");\r
453 \r
454         villageData.dumpXml(writer);\r
455 \r
456         writer.append("\n<!-- EOF -->\n");\r
457 \r
458         writer.flush();\r
459 \r
460         return;\r
461     }\r
462 \r
463     /**\r
464      * 村情報を反映した出力ファイル名を生成する。\r
465      * @param village 村情報\r
466      * @return XML出力ファイル名\r
467      */\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
472 \r
473         String fname =\r
474                 MessageFormat.format(\r
475                 "{0}jin_{1}_{2,number,#00000}.xml", OUTPATH, landId, vid);\r
476         return fname;\r
477     }\r
478 \r
479     /**\r
480      * 村情報を反映した出力ファイルへの文字ストリームを生成する。\r
481      * @param village 村情報\r
482      * @return 出力先文字ストリーム\r
483      * @throws IOException 出力エラー\r
484      */\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
489 \r
490         OutputStream ostream;\r
491         ostream = new FileOutputStream(file);\r
492         ostream = new BufferedOutputStream(ostream, 10000);\r
493         Writer writer;\r
494         writer = new OutputStreamWriter(ostream, "UTF-8");\r
495         writer = new BufferedWriter(writer, 10000);\r
496         return writer;\r
497     }\r
498 \r
499 }\r