OSDN Git Service

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