OSDN Git Service

1.105.3-SNAPSHOT版開発開始
[mikutoga/TogaGem.git] / src / main / java / jp / sourceforge / mikutoga / xml / BasicXmlExporter.java
1 /*\r
2  * basic xml exporter\r
3  *\r
4  * License : The MIT License\r
5  * Copyright(c) 2010 MikuToga Partners\r
6  */\r
7 \r
8 package jp.sourceforge.mikutoga.xml;\r
9 \r
10 import java.io.BufferedWriter;\r
11 import java.io.Closeable;\r
12 import java.io.Flushable;\r
13 import java.io.IOException;\r
14 import java.io.OutputStream;\r
15 import java.io.OutputStreamWriter;\r
16 import java.nio.charset.Charset;\r
17 import javax.xml.bind.DatatypeConverter;\r
18 \r
19 /**\r
20  * 各種XMLエクスポータの基本機能。\r
21  * UCS4は未サポート。\r
22  */\r
23 public class BasicXmlExporter {\r
24 \r
25     /** デフォルトエンコーディング。 */\r
26     private static final Charset CS_UTF8 = Charset.forName("UTF-8");\r
27 \r
28     /** デフォルトの改行文字列。 */\r
29     private static final String LF = "\n";       // 0x0a\r
30     /** デフォルトのインデント単位。 */\r
31     private static final String DEFAULT_INDENT_UNIT = "\u0020\u0020";\r
32 \r
33     private static final char[] HEXCHAR_TABLE = {\r
34         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',\r
35         'A', 'B', 'C', 'D', 'E', 'F',\r
36     };\r
37 \r
38     static{\r
39         assert HEXCHAR_TABLE.length == 16;\r
40     }\r
41 \r
42 \r
43     private final Appendable appendable;\r
44 \r
45     private String newline = LF;\r
46     private String indentUnit = DEFAULT_INDENT_UNIT;\r
47 \r
48     private int indentNest = 0;\r
49     private boolean basicLatinOnlyOut = true;\r
50 \r
51 \r
52     /**\r
53      * コンストラクタ。\r
54      * 文字エンコーディングはUTF-8が用いられる。\r
55      * @param stream 出力ストリーム\r
56      */\r
57     public BasicXmlExporter(OutputStream stream){\r
58         this(stream, CS_UTF8);\r
59         return;\r
60     }\r
61 \r
62     /**\r
63      * コンストラクタ。\r
64      * @param stream 出力ストリーム\r
65      * @param charSet 文字エンコーディング指定\r
66      */\r
67     public BasicXmlExporter(OutputStream stream, Charset charSet){\r
68         this(\r
69             new BufferedWriter(\r
70                 new OutputStreamWriter(stream, charSet)\r
71             )\r
72         );\r
73         return;\r
74     }\r
75 \r
76     /**\r
77      * コンストラクタ。\r
78      * @param appendable 文字列出力\r
79      */\r
80     public BasicXmlExporter(Appendable appendable){\r
81         super();\r
82         this.appendable = appendable;\r
83         return;\r
84     }\r
85 \r
86     /**\r
87      * ASCIIコード相当(UCS:Basic-Latin)の文字か否か判定する。\r
88      * @param ch 判定対象文字\r
89      * @return Basic-Latin文字ならtrue\r
90      */\r
91     public static boolean isBasicLatin(char ch){\r
92         if(ch <= 0x7f) return true;\r
93         return false;\r
94     }\r
95 \r
96     /**\r
97      * 改行文字列を設定する。\r
98      * @param newLine 改行文字列\r
99      * @throws NullPointerException 引数がnull\r
100      */\r
101     public void setNewLine(String newLine) throws NullPointerException{\r
102         if(newLine == null) throw new NullPointerException();\r
103         this.newline = newLine;\r
104         return;\r
105     }\r
106 \r
107     /**\r
108      * BasicLatin文字だけで出力するか設定する。\r
109      * BasicLatin以外の文字(≒日本語)をそのまま出力するか\r
110      * 文字参照で出力するかの設定が可能。\r
111      * コメント部中身は対象外。\r
112      * @param bool BasicLatin文字だけで出力するならtrue\r
113      */\r
114     public void setBasicLatinOnlyOut(boolean bool){\r
115         this.basicLatinOnlyOut = bool;\r
116         return;\r
117     }\r
118 \r
119     /**\r
120      * BasicLatin文字だけを出力する状態か判定する。\r
121      * コメント部中身は対象外。\r
122      * @return BasicLatin文字だけで出力するならtrue\r
123      */\r
124     public boolean isBasicLatinOnlyOut(){\r
125         return this.basicLatinOnlyOut;\r
126     }\r
127 \r
128     /**\r
129      * 改行文字列を設定する。\r
130      * デフォルトではLF(0x0a)\nが用いられる。\r
131      * @param seq 改行文字列。nullは空文字列""と解釈される。\r
132      */\r
133     public void setNewLine(CharSequence seq){\r
134         if(seq == null) this.newline = "";\r
135         else            this.newline = seq.toString();\r
136         return;\r
137     }\r
138 \r
139     /**\r
140      * インデント単位文字列を設定する。\r
141      * デフォルトでは空白2個。\r
142      * @param seq インデント単位文字列。nullは空文字列""と解釈される。\r
143      */\r
144     public void setIndentUnit(CharSequence seq){\r
145         if(seq == null) this.indentUnit = "";\r
146         else            this.indentUnit = seq.toString();\r
147     }\r
148 \r
149     /**\r
150      * 可能であれば出力をフラッシュする。\r
151      * @throws IOException 出力エラー\r
152      */\r
153     public void flush() throws IOException{\r
154         if(this.appendable instanceof Flushable){\r
155             ((Flushable)this.appendable).flush();\r
156         }\r
157         return;\r
158     }\r
159 \r
160     /**\r
161      * 可能であれば出力をクローズする。\r
162      * @throws IOException 出力エラー\r
163      */\r
164     public void close() throws IOException{\r
165         if(this.appendable instanceof Closeable){\r
166             ((Closeable)this.appendable).close();\r
167         }\r
168         return;\r
169     }\r
170 \r
171     /**\r
172      * 1文字出力する。\r
173      * @param ch 文字\r
174      * @return this本体\r
175      * @throws IOException 出力エラー\r
176      */\r
177     public BasicXmlExporter put(char ch) throws IOException{\r
178         this.appendable.append(ch);\r
179         return this;\r
180     }\r
181 \r
182     /**\r
183      * 文字列を出力する。\r
184      * @param seq 文字列\r
185      * @return this本体\r
186      * @throws IOException 出力エラー\r
187      */\r
188     public BasicXmlExporter put(CharSequence seq) throws IOException{\r
189         this.appendable.append(seq);\r
190         return this;\r
191     }\r
192 \r
193     /**\r
194      * int値を出力する。\r
195      * @param iVal int値\r
196      * @return this本体\r
197      * @throws IOException 出力エラー\r
198      * @see java.lang.Integer#toString(int)\r
199      */\r
200     public BasicXmlExporter put(int iVal) throws IOException{\r
201         String value = DatatypeConverter.printInt(iVal);\r
202         this.appendable.append(value);\r
203         return this;\r
204     }\r
205 \r
206     /**\r
207      * float値を出力する。\r
208      * @param fVal float値\r
209      * @return this本体\r
210      * @throws IOException 出力エラー\r
211      * @see java.lang.Float#toString(float)\r
212      */\r
213     public BasicXmlExporter put(float fVal) throws IOException{\r
214         String value = DatatypeConverter.printFloat(fVal);\r
215         this.appendable.append(value);\r
216         return this;\r
217     }\r
218 \r
219     /**\r
220      * 改行を出力する。\r
221      * @return this本体\r
222      * @throws IOException 出力エラー\r
223      */\r
224     public BasicXmlExporter ln() throws IOException{\r
225         this.appendable.append(this.newline);\r
226         return this;\r
227     }\r
228 \r
229     /**\r
230      * 改行を指定回数出力する。\r
231      * @param count 改行回数。0以下の場合は何も出力しない。\r
232      * @return this本体\r
233      * @throws IOException 出力エラー\r
234      */\r
235     public BasicXmlExporter ln(int count) throws IOException{\r
236         for(int ct = 1; ct <= count; ct++){\r
237             this.appendable.append(this.newline);\r
238         }\r
239         return this;\r
240     }\r
241 \r
242     /**\r
243      * インデントを出力する。\r
244      * インデント単位文字列をネストレベル回数分出力する。\r
245      * @return this本体\r
246      * @throws IOException 出力エラー\r
247      */\r
248     public BasicXmlExporter ind() throws IOException{\r
249         for(int ct = 1; ct <= this.indentNest; ct++){\r
250             put(this.indentUnit);\r
251         }\r
252         return this;\r
253     }\r
254 \r
255     /**\r
256      * インデントレベルを一段下げる。\r
257      */\r
258     public void pushNest(){\r
259         this.indentNest++;\r
260         return;\r
261     }\r
262 \r
263     /**\r
264      * インデントレベルを一段上げる。\r
265      * インデントレベル0の状態をさらに上げようとした場合、何も起こらない。\r
266      */\r
267     public void popNest(){\r
268         this.indentNest--;\r
269         if(this.indentNest < 0) this.indentNest = 0;\r
270         return;\r
271     }\r
272 \r
273     /**\r
274      * 指定された文字を16進2桁の文字参照形式で出力する。\r
275      * 2桁で出力できない場合は4桁で出力する。\r
276      * @param ch 文字\r
277      * @return this本体\r
278      * @throws IOException 出力エラー\r
279      */\r
280     public BasicXmlExporter putCharRef2Hex(char ch) throws IOException{\r
281         if(ch > 0xff) return putCharRef4Hex(ch);\r
282 \r
283         char hex3 = HEXCHAR_TABLE[(ch >> 4) & 0x000f];\r
284         char hex4 = HEXCHAR_TABLE[(ch     ) & 0x000f];\r
285 \r
286         this.appendable.append("&#x");\r
287         this.appendable.append(hex3);\r
288         this.appendable.append(hex4);\r
289         this.appendable.append(';');\r
290 \r
291         return this;\r
292     }\r
293 \r
294     /**\r
295      * 指定された文字を16進4桁の文字参照形式で出力する。\r
296      * UCS4に伴うサロゲートペアは未サポート\r
297      * @param ch 文字\r
298      * @return this本体\r
299      * @throws IOException 出力エラー\r
300      */\r
301     public BasicXmlExporter putCharRef4Hex(char ch) throws IOException{\r
302         char hex1 = HEXCHAR_TABLE[(ch >> 12) & 0x000f];\r
303         char hex2 = HEXCHAR_TABLE[(ch >>  8) & 0x000f];\r
304         char hex3 = HEXCHAR_TABLE[(ch >>  4) & 0x000f];\r
305         char hex4 = HEXCHAR_TABLE[(ch      ) & 0x000f];\r
306 \r
307         this.appendable.append("&#x");\r
308         this.appendable.append(hex1);\r
309         this.appendable.append(hex2);\r
310         this.appendable.append(hex3);\r
311         this.appendable.append(hex4);\r
312         this.appendable.append(';');\r
313 \r
314         return this;\r
315     }\r
316 \r
317     /**\r
318      * 要素の中身および属性値中身を出力する。\r
319      * 必要に応じてXML定義済み実体文字が割り振られた文字、\r
320      * コントロールコード、および非BasicLatin文字がエスケープされる。\r
321      * @param content 内容\r
322      * @return this本体\r
323      * @throws IOException 出力エラー\r
324      */\r
325     public BasicXmlExporter putContent(CharSequence content)\r
326             throws IOException{\r
327         int length = content.length();\r
328 \r
329         for(int pos = 0; pos < length; pos++){\r
330             char ch = content.charAt(pos);\r
331             if(Character.isISOControl(ch)){\r
332                 putCharRef2Hex(ch);\r
333             }else if( ! isBasicLatin(ch) && isBasicLatinOnlyOut()){\r
334                 putCharRef4Hex(ch);\r
335             }else{\r
336                 switch(ch){\r
337                 case '&':  this.appendable.append("&amp;");  break;\r
338                 case '<':  this.appendable.append("&lt;");   break;\r
339                 case '>':  this.appendable.append("&gt;");   break;\r
340                 case '"':  this.appendable.append("&quot;"); break;\r
341                 case '\'': this.appendable.append("&apos;"); break;\r
342                 case '\u00a5': this.appendable.append('\u005c\u005c'); break;\r
343                 default:   this.appendable.append(ch);       break;\r
344                 }\r
345             }\r
346         }\r
347 \r
348         return this;\r
349     }\r
350 \r
351     /**\r
352      * 属性値を出力する。\r
353      * @param attrName 属性名\r
354      * @param content 属性内容\r
355      * @return this本体\r
356      * @throws IOException 出力エラー\r
357      */\r
358     public BasicXmlExporter putAttr(CharSequence attrName,\r
359                                      CharSequence content)\r
360             throws IOException{\r
361         put(attrName).put('=').put('"').putContent(content).put('"');\r
362         return this;\r
363     }\r
364 \r
365     /**\r
366      * int型属性値を出力する。\r
367      * @param attrName 属性名\r
368      * @param iVal int値\r
369      * @return this本体\r
370      * @throws IOException 出力エラー\r
371      */\r
372     public BasicXmlExporter putIntAttr(CharSequence attrName,\r
373                                            int iVal)\r
374             throws IOException{\r
375         put(attrName).put('=').put('"').put(iVal).put('"');\r
376         return this;\r
377     }\r
378 \r
379     /**\r
380      * float型属性値を出力する。\r
381      * @param attrName 属性名\r
382      * @param fVal float値\r
383      * @return this本体\r
384      * @throws IOException 出力エラー\r
385      */\r
386     public BasicXmlExporter putFloatAttr(CharSequence attrName,\r
387                                               float fVal)\r
388             throws IOException{\r
389         put(attrName).put('=').put('"').put(fVal).put('"');\r
390         return this;\r
391     }\r
392 \r
393     /**\r
394      * コメントの内容を出力する。\r
395      * コメント中の\n記号出現に伴い、\r
396      * あらかじめ指定された改行文字が出力される。\r
397      * \n以外のコントロールコード各種、\r
398      * 及び非BasicLatin文字はそのまま出力される。\r
399      * 連続するハイフン(-)記号間には強制的にスペースが挿入される。\r
400      * @param comment コメント内容\r
401      * @return this本体\r
402      * @throws IOException 出力エラー\r
403      */\r
404     public BasicXmlExporter putCommentContent(CharSequence comment)\r
405             throws IOException{\r
406         int length = comment.length();\r
407 \r
408         char prev = '\0';\r
409         for(int pos = 0; pos < length; pos++){\r
410             char ch = comment.charAt(pos);\r
411             if(ch == '\n'){\r
412                 ln();\r
413                 prev = ch;\r
414                 continue;\r
415             }\r
416             if(prev == '-' && ch == '-') put(' ');\r
417             put(ch);\r
418             prev = ch;\r
419         }\r
420 \r
421         return this;\r
422     }\r
423 \r
424     /**\r
425      * 1行コメントを出力する。\r
426      * コメント中の\n記号出現に伴い、\r
427      * あらかじめ指定された改行文字が出力される。\r
428      * \n以外のコントロールコード各種、\r
429      * 及び非BasicLatin文字はそのまま出力される。\r
430      * 連続するハイフン(-)記号間には強制的にスペースが挿入される。\r
431      * @param comment コメント内容\r
432      * @return this本体\r
433      * @throws IOException 出力エラー\r
434      */\r
435     public BasicXmlExporter putLineComment(CharSequence comment)\r
436             throws IOException{\r
437         put("<!--").put(' ');\r
438         putCommentContent(comment);\r
439         put(' ').put("-->");\r
440         return this;\r
441     }\r
442 \r
443     /**\r
444      * ブロックコメントを出力する。\r
445      * コメント中の\n記号出現に伴い、\r
446      * あらかじめ指定された改行文字が出力される。\r
447      * \n以外のコントロールコード各種、\r
448      * 及び非BasicLatin文字はそのまま出力される。\r
449      * 連続するハイフン(-)記号間には強制的にスペースが挿入される。\r
450      * @param comment コメント内容\r
451      * @return this本体\r
452      * @throws IOException 出力エラー\r
453      */\r
454     public BasicXmlExporter putBlockComment(CharSequence comment)\r
455             throws IOException{\r
456         put("<!--").ln();\r
457 \r
458         putCommentContent(comment);\r
459 \r
460         int commentLength = comment.length();\r
461         if(commentLength > 0){\r
462             char lastCh = comment.charAt(commentLength - 1);\r
463             if(lastCh != '\n') ln();\r
464         }\r
465 \r
466         put("-->").ln();\r
467 \r
468         return this;\r
469     }\r
470 \r
471 }\r