OSDN Git Service

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