OSDN Git Service

110420版スキーマに対処。
[jindolf/JinArchiver.git] / src / main / java / jp / sourceforge / jindolf / archiver / XmlUtils.java
1 /*\r
2  * XML utils\r
3  *\r
4  * Copyright(c) 2008 olyutorskii\r
5  */\r
6 \r
7 package jp.sourceforge.jindolf.archiver;\r
8 \r
9 import java.io.BufferedOutputStream;\r
10 import java.io.BufferedWriter;\r
11 import java.io.File;\r
12 import java.io.FileOutputStream;\r
13 import java.io.IOException;\r
14 import java.io.OutputStream;\r
15 import java.io.OutputStreamWriter;\r
16 import java.io.Writer;\r
17 import java.text.MessageFormat;\r
18 import java.util.Calendar;\r
19 import java.util.GregorianCalendar;\r
20 import java.util.List;\r
21 import java.util.TimeZone;\r
22 import jp.sourceforge.jindolf.corelib.LandDef;\r
23 import jp.sourceforge.jindolf.parser.DecodeErrorInfo;\r
24 import jp.sourceforge.jindolf.parser.DecodedContent;\r
25 \r
26 /**\r
27  * XML用各種ユーティリティ\r
28  */\r
29 public final class XmlUtils{\r
30 \r
31     public static final String ORIG_DTD =\r
32             "http://jindolf.sourceforge.jp/xml/dtd/bbsArchive-110420.dtd";\r
33     public static final String ORIG_NS =\r
34             "http://jindolf.sourceforge.jp/xml/ns/501";\r
35     public static final String ORIG_SCHEME =\r
36             "http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-110420.xsd";\r
37     public static final String SCHEMA_NS =\r
38             "http://www.w3.org/2001/XMLSchema-instance";\r
39 \r
40     public static final String OUTPATH = "D:\\TEMP\\zxzx\\";\r
41 \r
42     private static final String INDENT_UNIT = "\u0020\u0020";\r
43 \r
44     private static final TimeZone TZ_TOKYO =\r
45             TimeZone.getTimeZone("Asia/Tokyo");\r
46     private static final GregorianCalendar calendar =\r
47             new GregorianCalendar(TZ_TOKYO);\r
48 \r
49     /**\r
50      * DOCTYPE宣言を出力する。\r
51      * @param writer 出力先\r
52      * @throws IOException 出力エラー\r
53      */\r
54     public static void dumpDocType(Writer writer) throws IOException{\r
55         writer.append("<!DOCTYPE village SYSTEM ");\r
56         writer.append('"');\r
57         writer.append(ORIG_DTD);\r
58         writer.append('"');\r
59         writer.append(" >");\r
60         return;\r
61     }\r
62 \r
63     /**\r
64      * オリジナルNameSpace宣言を出力する。\r
65      * @param writer 出力先\r
66      * @throws IOException 出力エラー\r
67      */\r
68     public static void dumpNameSpaceDecl(Writer writer)\r
69             throws IOException{\r
70         attrOut(writer, "xmlns", ORIG_NS);\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 dumpSiNameSpaceDecl(Writer writer)\r
80             throws IOException{\r
81         attrOut(writer, "xmlns:xsi", SCHEMA_NS);\r
82         return;\r
83     }\r
84 \r
85     /**\r
86      * スキーマ位置指定を出力する。\r
87      * @param writer 出力先\r
88      * @throws IOException 出力エラー\r
89      */\r
90     public static void dumpSchemeLocation(Writer writer)\r
91             throws IOException{\r
92         attrOut(writer,\r
93                 "xsi:schemaLocation",\r
94                 ORIG_NS + " " + ORIG_SCHEME);\r
95         return;\r
96     }\r
97 \r
98     /**\r
99      * インデント用空白を出力する。\r
100      * ネスト単位は空白2文字\r
101      * @param writer 出力先\r
102      * @param level ネストレベル\r
103      * @throws IOException 出力エラー\r
104      */\r
105     public static void indent(Writer writer, int level) throws IOException{\r
106         for(int ct = 1; ct <= level; ct++){\r
107             writer.append(INDENT_UNIT);\r
108         }\r
109         return;\r
110     }\r
111 \r
112     /**\r
113      * XML数値文字参照を出力する。\r
114      * @param writer 出力先\r
115      * @param chVal 出力文字\r
116      * @throws IOException 出力エラー\r
117      */\r
118     public static void charRefOut(Writer writer, char chVal)\r
119             throws IOException{\r
120         if(chVal == '\u0020'){\r
121             writer.append("&#x20;");\r
122             return;\r
123         }\r
124 \r
125         if(chVal == '\u0009'){\r
126             writer.append("&#x09;");\r
127             return;\r
128         }\r
129 \r
130         int ival = 0xffff & ((int) chVal);\r
131         String hex = Integer.toHexString(ival);\r
132         if(hex.length() % 2 != 0) hex = "0" + hex;\r
133 \r
134         writer.append("&#x");\r
135         writer.append(hex);\r
136         writer.append(";");\r
137 \r
138         return;\r
139     }\r
140 \r
141     /**\r
142      * 不正文字をXML出力する。\r
143      * @param writer 出力先\r
144      * @param chVal 不正文字\r
145      * @throws IOException 出力エラー\r
146      */\r
147     public static void dumpInvalidChar(Writer writer, char chVal)\r
148             throws IOException{\r
149         int hexVal;\r
150         hexVal = chVal & 0xff;\r
151         String hexBin = Integer.toHexString(hexVal);\r
152         if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;\r
153 \r
154         char replaceChar = '\ufffd';\r
155         if('\u0000' <= chVal && chVal <= '\u001f'){\r
156             replaceChar = (char)( chVal + '\u2400' );\r
157         }\r
158 \r
159         writer.append("<rawdata");\r
160 \r
161         writer.append(' ');\r
162         attrOut(writer, "encoding", "Shift_JIS");\r
163 \r
164         writer.append(' ');\r
165         attrOut(writer, "hexBin", hexBin);\r
166 \r
167         writer.append(" >");\r
168         writer.append(replaceChar);\r
169         writer.append("</rawdata>");\r
170     }\r
171 \r
172     /**\r
173      * 任意の文字がXML規格上のホワイトスペースに属するか判定する。\r
174      * @param chVal 文字\r
175      * @return ホワイトスペースならtrue\r
176      */\r
177     public static boolean isWhiteSpace(char chVal){\r
178         switch(chVal){\r
179         case '\u0020':\r
180         case '\t':\r
181         case '\n':\r
182         case '\r':\r
183             return true;\r
184         default:\r
185             break;\r
186         }\r
187 \r
188         return false;\r
189     }\r
190 \r
191     /**\r
192      * 文字列を出力する。\r
193      * <ul>\r
194      * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。\r
195      * <li>連続したホワイトスペースの2文字目以降は文字参照化される。\r
196      * <li>スペースでないホワイトスペースは無条件に文字参照化される。\r
197      * <li>{@literal &, <, >, "}は無条件に文字参照化される。\r
198      * </ul>\r
199      * 参考:XML 1.0 規格 3.3.3節\r
200      * @param writer 出力先\r
201      * @param seq CDATA文字列\r
202      * @throws IOException 出力エラー\r
203      */\r
204     public static void textOut(Writer writer, CharSequence seq)\r
205             throws IOException{\r
206         int len = seq.length();\r
207 \r
208         boolean leadSpace = false;\r
209 \r
210         for(int pos = 0; pos < len; pos++){\r
211             char chVal = seq.charAt(pos);\r
212 \r
213             if(isWhiteSpace(chVal)){\r
214                 if(pos == 0 || pos >= len - 1 || leadSpace){\r
215                     charRefOut(writer, chVal);\r
216                 }else if(chVal != '\u0020'){\r
217                     charRefOut(writer, chVal);\r
218                 }else{\r
219                     writer.append(chVal);\r
220                 }\r
221                 leadSpace = true;\r
222             }else{\r
223                 if(chVal == '&'){\r
224                     writer.append("&amp;");\r
225                 }else if(chVal == '<'){\r
226                     writer.append("&lt;");\r
227                 }else if(chVal == '>'){\r
228                     writer.append("&gt;");\r
229                 }else if(chVal == '"'){\r
230                     writer.append("&quot;");\r
231                 }else if(chVal == '\''){\r
232                     writer.append("&apos;");\r
233                 }else if(chVal == '\u005c\u005c'){\r
234                     writer.append('\u00a5');\r
235                 }else if(chVal == '\u007e'){\r
236                     writer.append('\u203e');\r
237                 }else if(Character.isISOControl(chVal)){\r
238                     dumpInvalidChar(writer, chVal);\r
239                 }else{\r
240                     writer.append(chVal);\r
241                 }\r
242                 leadSpace = false;\r
243             }\r
244         }\r
245 \r
246         return;\r
247     }\r
248 \r
249     /**\r
250      * 属性を出力する。\r
251      * @param writer 出力先\r
252      * @param name 属性名\r
253      * @param value 属性値\r
254      * @throws IOException 出力エラー\r
255      */\r
256     public static void attrOut(Writer writer,\r
257                                 CharSequence name,\r
258                                 CharSequence value)\r
259             throws IOException{\r
260         StringBuilder newValue = new StringBuilder(value);\r
261         for(int pt = 0; pt < newValue.length(); pt++){\r
262             char chVal = newValue.charAt(pt);\r
263             if(chVal == '\n' || chVal == '\r' || chVal == '\t') continue;\r
264             if(Character.isISOControl(chVal)){\r
265                 newValue.setCharAt(pt, (char)('\u2400' + chVal));\r
266             }\r
267         }\r
268 \r
269         writer.append(name);\r
270         writer.append('=');\r
271         writer.append('"');\r
272         textOut(writer, newValue);\r
273         writer.append('"');\r
274         return;\r
275     }\r
276 \r
277     /**\r
278      * xsd:time形式の時刻属性を出力する。\r
279      * タイムゾーンは「+09:00」固定\r
280      * @param writer 出力先\r
281      * @param name 属性名\r
282      * @param hour 時間\r
283      * @param minute 分\r
284      * @throws IOException 出力エラー\r
285      */\r
286     public static void timeAttrOut(Writer writer,\r
287                                      CharSequence name,\r
288                                      int hour, int minute)\r
289             throws IOException{\r
290         String cmtTime =\r
291                 MessageFormat\r
292                 .format("{0,number,#00}:{1,number,#00}:00+09:00",\r
293                         hour, minute);\r
294         attrOut(writer, name, cmtTime);\r
295         return;\r
296     }\r
297 \r
298     /**\r
299      * xsd:gMonthDay形式の日付属性を出力する。\r
300      * タイムゾーンは「+09:00」固定\r
301      * @param writer 出力先\r
302      * @param name 属性名\r
303      * @param month 月\r
304      * @param day 日\r
305      * @throws IOException 出力エラー\r
306      */\r
307     public static void dateAttrOut(Writer writer,\r
308                                      CharSequence name,\r
309                                      int month, int day)\r
310             throws IOException{\r
311         String dateAttr =\r
312                 MessageFormat.format("--{0,number,#00}-{1,number,#00}+09:00",\r
313                                      month, day);\r
314         attrOut(writer, name, dateAttr);\r
315         return;\r
316     }\r
317 \r
318     /**\r
319      * xsd:dateTime形式の日付時刻属性を出力する。\r
320      * タイムゾーンは「+09:00」固定\r
321      * @param writer 出力先\r
322      * @param name 属性名\r
323      * @param epochMs エポック時刻\r
324      * @throws IOException 出力エラー\r
325      */\r
326     public static void dateTimeAttr(Writer writer,\r
327                                       CharSequence name,\r
328                                       long epochMs)\r
329             throws IOException{\r
330         synchronized(calendar){\r
331             calendar.setTimeInMillis(epochMs);\r
332             int year = calendar.get(Calendar.YEAR);\r
333             int month = calendar.get(Calendar.MONTH) + 1;\r
334             int day = calendar.get(Calendar.DATE);\r
335             int hour = calendar.get(Calendar.HOUR_OF_DAY);\r
336             int minute = calendar.get(Calendar.MINUTE);\r
337             int sec = calendar.get(Calendar.SECOND);\r
338             int msec = calendar.get(Calendar.MILLISECOND);\r
339 \r
340             String attrVal = MessageFormat.format(\r
341                      "{0,number,#0000}-{1,number,#00}-{2,number,#00}"\r
342                     +"T{3,number,#00}:{4,number,#00}:{5,number,#00}"\r
343                     +".{6,number,#000}+09:00",\r
344                     year, month, day, hour, minute, sec, msec);\r
345 \r
346             attrOut(writer, name, attrVal);\r
347         }\r
348         return;\r
349     }\r
350 \r
351     /**\r
352      * デコードエラー情報をrawdataタグで出力する。\r
353      * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。\r
354      * @param writer 出力先\r
355      * @param errorInfo デコードエラー\r
356      * @throws IOException 出力エラー\r
357      */\r
358     public static void dumpErrorInfo(Writer writer,\r
359                                        DecodeErrorInfo errorInfo)\r
360             throws IOException{\r
361         int hexVal;\r
362         hexVal = errorInfo.getRawByte1st() & 0xff;\r
363         if(errorInfo.has2nd()){\r
364             hexVal <<= 8;\r
365             hexVal |= errorInfo.getRawByte2nd() & 0xff;\r
366         }\r
367 \r
368         String hexBin = Integer.toHexString(hexVal);\r
369         if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;\r
370 \r
371         char replaceChar = Win31j.getWin31jChar(errorInfo);\r
372 \r
373         writer.append("<rawdata");\r
374 \r
375         writer.append(' ');\r
376         attrOut(writer, "encoding", "Shift_JIS");\r
377 \r
378         writer.append(' ');\r
379         attrOut(writer, "hexBin", hexBin);\r
380 \r
381         writer.append(" >");\r
382         writer.append(replaceChar);\r
383         writer.append("</rawdata>");\r
384 \r
385         return;\r
386     }\r
387 \r
388     /**\r
389      * デコードエラー込みのテキストを出力する。\r
390      * @param writer 出力先\r
391      * @param content テキスト\r
392      * @throws IOException 出力エラー\r
393      */\r
394     public static void dumpDecodedContent(Writer writer,\r
395                                              DecodedContent content)\r
396             throws IOException{\r
397         if( ! content.hasDecodeError() ){\r
398             textOut(writer, content);\r
399             return;\r
400         }\r
401 \r
402         int last = 0;\r
403 \r
404         List<DecodeErrorInfo> errList = content.getDecodeErrorList();\r
405         for(DecodeErrorInfo err : errList){\r
406             int charPos = err.getCharPosition();\r
407             CharSequence line = content.subSequence(last, charPos);\r
408             textOut(writer, line);\r
409             dumpErrorInfo(writer, err);\r
410             last = charPos + 1;\r
411         }\r
412 \r
413         CharSequence line = content.subSequence(last, content.length());\r
414         textOut(writer, line);\r
415 \r
416         return;\r
417     }\r
418 \r
419     /**\r
420      * 村情報をXML形式で出力する。\r
421      * @param writer 出力先\r
422      * @param villageData 村情報\r
423      * @throws IOException 出力エラー\r
424      */\r
425     public static void dumpVillageData(Writer writer,\r
426                                          VillageData villageData)\r
427             throws IOException{\r
428         writer.append("<?xml");\r
429         writer.append(' ');\r
430         attrOut(writer, "version", "1.0");\r
431         writer.append(' ');\r
432         attrOut(writer, "encoding", "UTF-8");\r
433         writer.append(" ?>\n\n");\r
434 \r
435         writer.append("<!--\n");\r
436         writer.append("  人狼BBSアーカイブ\n");\r
437         writer.append("  http://jindolf.sourceforge.jp/\n");\r
438         writer.append("-->\n\n");\r
439 \r
440         dumpDocType(writer);\r
441         writer.append("\n\n");\r
442 \r
443         villageData.dumpXml(writer);\r
444 \r
445         writer.append("\n<!-- EOF -->\n");\r
446 \r
447         writer.flush();\r
448 \r
449         return;\r
450     }\r
451 \r
452     /**\r
453      * 村情報を反映した出力ファイル名を生成する。\r
454      * @param village 村情報\r
455      * @return XML出力ファイル名\r
456      */\r
457     public static String createOutFileName(VillageData village){\r
458         LandDef landDef = village.getLandDef();\r
459         String landId = landDef.getLandId();\r
460         int vid = village.getVillageId();\r
461 \r
462         String fname =\r
463                 MessageFormat.format(\r
464                 "{0}jin_{1}_{2,number,#00000}.xml", OUTPATH, landId, vid);\r
465         return fname;\r
466     }\r
467 \r
468     /**\r
469      * 村情報を反映した出力ファイルへの文字ストリームを生成する。\r
470      * @param village 村情報\r
471      * @return 出力先文字ストリーム\r
472      * @throws IOException 出力エラー\r
473      */\r
474     public static Writer createFileWriter(VillageData village)\r
475             throws IOException{\r
476         String fname = createOutFileName(village);\r
477         File file = new File(fname);\r
478 \r
479         OutputStream ostream;\r
480         ostream = new FileOutputStream(file);\r
481         ostream = new BufferedOutputStream(ostream, 10000);\r
482         Writer writer;\r
483         writer = new OutputStreamWriter(ostream, "UTF-8");\r
484         writer = new BufferedWriter(writer, 10000);\r
485         return writer;\r
486     }\r
487 \r
488     /**\r
489      * 隠れコンストラクタ\r
490      */\r
491     private XmlUtils(){\r
492         throw new Error();\r
493     }\r
494 \r
495 }\r