4 * License : The MIT License
5 * Copyright(c) 2010 MikuToga Partners
8 package jp.sourceforge.mikutoga.xml;
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;
23 public class BasicXmlExporter {
26 private static final Charset CS_UTF8 = Charset.forName("UTF-8");
29 private static final String LF = "\n"; // 0x0a
31 private static final String DEFAULT_INDENT_UNIT = "\u0020\u0020";
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',
39 assert HEXCHAR_TABLE.length == 16;
43 private final Appendable appendable;
45 private String newline = LF;
46 private String indentUnit = DEFAULT_INDENT_UNIT;
48 private int indentNest = 0;
49 private boolean basicLatinOnlyOut = true;
54 * 文字エンコーディングはUTF-8が用いられる。
55 * @param stream 出力ストリーム
57 public BasicXmlExporter(OutputStream stream){
58 this(stream, CS_UTF8);
64 * @param stream 出力ストリーム
65 * @param charSet 文字エンコーディング指定
67 public BasicXmlExporter(OutputStream stream, Charset charSet){
70 new OutputStreamWriter(stream, charSet)
78 * @param appendable 文字列出力
80 public BasicXmlExporter(Appendable appendable){
82 this.appendable = appendable;
87 * ASCIIコード相当(UCS:Basic-Latin)の文字か否か判定する。
89 * @return Basic-Latin文字ならtrue
91 public static boolean isBasicLatin(char ch){
92 if(ch <= 0x7f) return true;
98 * @param newLine 改行文字列
99 * @throws NullPointerException 引数がnull
101 public void setNewLine(String newLine) throws NullPointerException{
102 if(newLine == null) throw new NullPointerException();
103 this.newline = newLine;
108 * BasicLatin文字だけで出力するか設定する。
109 * BasicLatin以外の文字(≒日本語)をそのまま出力するか
112 * @param bool BasicLatin文字だけで出力するならtrue
114 public void setBasicLatinOnlyOut(boolean bool){
115 this.basicLatinOnlyOut = bool;
120 * BasicLatin文字だけを出力する状態か判定する。
122 * @return BasicLatin文字だけで出力するならtrue
124 public boolean isBasicLatinOnlyOut(){
125 return this.basicLatinOnlyOut;
130 * デフォルトではLF(0x0a)\nが用いられる。
131 * @param seq 改行文字列。nullは空文字列""と解釈される。
133 public void setNewLine(CharSequence seq){
134 if(seq == null) this.newline = "";
135 else this.newline = seq.toString();
142 * @param seq インデント単位文字列。nullは空文字列""と解釈される。
144 public void setIndentUnit(CharSequence seq){
145 if(seq == null) this.indentUnit = "";
146 else this.indentUnit = seq.toString();
151 * @throws IOException 出力エラー
153 public void flush() throws IOException{
154 if(this.appendable instanceof Flushable){
155 ((Flushable)this.appendable).flush();
162 * @throws IOException 出力エラー
164 public void close() throws IOException{
165 if(this.appendable instanceof Closeable){
166 ((Closeable)this.appendable).close();
175 * @throws IOException 出力エラー
177 public BasicXmlExporter put(char ch) throws IOException{
178 this.appendable.append(ch);
186 * @throws IOException 出力エラー
188 public BasicXmlExporter put(CharSequence seq) throws IOException{
189 this.appendable.append(seq);
197 * @throws IOException 出力エラー
198 * @see java.lang.Integer#toString(int)
200 public BasicXmlExporter put(int iVal) throws IOException{
201 String value = DatatypeConverter.printInt(iVal);
202 this.appendable.append(value);
210 * @throws IOException 出力エラー
211 * @see java.lang.Float#toString(float)
213 public BasicXmlExporter put(float fVal) throws IOException{
214 String value = DatatypeConverter.printFloat(fVal);
215 this.appendable.append(value);
222 * @throws IOException 出力エラー
224 public BasicXmlExporter ln() throws IOException{
225 this.appendable.append(this.newline);
231 * @param count 改行回数。0以下の場合は何も出力しない。
233 * @throws IOException 出力エラー
235 public BasicXmlExporter ln(int count) throws IOException{
236 for(int ct = 1; ct <= count; ct++){
237 this.appendable.append(this.newline);
244 * インデント単位文字列をネストレベル回数分出力する。
246 * @throws IOException 出力エラー
248 public BasicXmlExporter ind() throws IOException{
249 for(int ct = 1; ct <= this.indentNest; ct++){
250 put(this.indentUnit);
258 public void pushNest(){
265 * インデントレベル0の状態をさらに上げようとした場合、何も起こらない。
267 public void popNest(){
269 if(this.indentNest < 0) this.indentNest = 0;
274 * 指定された文字を16進2桁の文字参照形式で出力する。
275 * 2桁で出力できない場合は4桁で出力する。
278 * @throws IOException 出力エラー
280 public BasicXmlExporter putCharRef2Hex(char ch) throws IOException{
281 if(ch > 0xff) return putCharRef4Hex(ch);
283 char hex3 = HEXCHAR_TABLE[(ch >> 4) & 0x000f];
284 char hex4 = HEXCHAR_TABLE[(ch ) & 0x000f];
286 this.appendable.append("&#x");
287 this.appendable.append(hex3);
288 this.appendable.append(hex4);
289 this.appendable.append(';');
295 * 指定された文字を16進4桁の文字参照形式で出力する。
296 * UCS4に伴うサロゲートペアは未サポート
299 * @throws IOException 出力エラー
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];
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(';');
318 * 要素の中身および属性値中身を出力する。
319 * 必要に応じてXML定義済み実体文字が割り振られた文字、
320 * コントロールコード、および非BasicLatin文字がエスケープされる。
323 * @throws IOException 出力エラー
325 public BasicXmlExporter putContent(CharSequence content)
327 int length = content.length();
329 for(int pos = 0; pos < length; pos++){
330 char ch = content.charAt(pos);
331 if(Character.isISOControl(ch)){
333 }else if( ! isBasicLatin(ch) && isBasicLatinOnlyOut()){
337 case '&': this.appendable.append("&"); break;
338 case '<': this.appendable.append("<"); break;
339 case '>': this.appendable.append(">"); break;
340 case '"': this.appendable.append("""); break;
341 case '\'': this.appendable.append("'"); break;
342 case '\u00a5': this.appendable.append('\u005c\u005c'); break;
343 default: this.appendable.append(ch); break;
353 * @param attrName 属性名
354 * @param content 属性内容
356 * @throws IOException 出力エラー
358 public BasicXmlExporter putAttr(CharSequence attrName,
359 CharSequence content)
361 put(attrName).put('=').put('"').putContent(content).put('"');
367 * @param attrName 属性名
370 * @throws IOException 出力エラー
372 public BasicXmlExporter putIntAttr(CharSequence attrName,
375 put(attrName).put('=').put('"').put(iVal).put('"');
381 * @param attrName 属性名
384 * @throws IOException 出力エラー
386 public BasicXmlExporter putFloatAttr(CharSequence attrName,
389 put(attrName).put('=').put('"').put(fVal).put('"');
396 * あらかじめ指定された改行文字が出力される。
398 * 及び非BasicLatin文字はそのまま出力される。
399 * 連続するハイフン(-)記号間には強制的にスペースが挿入される。
400 * @param comment コメント内容
402 * @throws IOException 出力エラー
404 public BasicXmlExporter putCommentContent(CharSequence comment)
406 int length = comment.length();
409 for(int pos = 0; pos < length; pos++){
410 char ch = comment.charAt(pos);
416 if(prev == '-' && ch == '-') put(' ');
427 * あらかじめ指定された改行文字が出力される。
429 * 及び非BasicLatin文字はそのまま出力される。
430 * 連続するハイフン(-)記号間には強制的にスペースが挿入される。
431 * @param comment コメント内容
433 * @throws IOException 出力エラー
435 public BasicXmlExporter putLineComment(CharSequence comment)
437 put("<!--").put(' ');
438 putCommentContent(comment);
446 * あらかじめ指定された改行文字が出力される。
448 * 及び非BasicLatin文字はそのまま出力される。
449 * 連続するハイフン(-)記号間には強制的にスペースが挿入される。
450 * @param comment コメント内容
452 * @throws IOException 出力エラー
454 public BasicXmlExporter putBlockComment(CharSequence comment)
458 putCommentContent(comment);
460 int commentLength = comment.length();
461 if(commentLength > 0){
462 char lastCh = comment.charAt(commentLength - 1);
463 if(lastCh != '\n') ln();