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);
245 * @throws IOException 出力エラー
247 public BasicXmlExporter sp() throws IOException{
248 this.appendable.append(" ");
254 * @param count 空白回数。0以下の場合は何も出力しない。
256 * @throws IOException 出力エラー
258 public BasicXmlExporter sp(int count) throws IOException{
259 for(int ct = 1; ct <= count; ct++){
260 this.appendable.append(" ");
267 * インデント単位文字列をネストレベル回数分出力する。
269 * @throws IOException 出力エラー
271 public BasicXmlExporter ind() throws IOException{
272 for(int ct = 1; ct <= this.indentNest; ct++){
273 put(this.indentUnit);
281 public void pushNest(){
288 * インデントレベル0の状態をさらに上げようとした場合、何も起こらない。
290 public void popNest(){
292 if(this.indentNest < 0) this.indentNest = 0;
297 * 指定された文字を16進2桁の文字参照形式で出力する。
298 * 2桁で出力できない場合は4桁で出力する。
301 * @throws IOException 出力エラー
303 public BasicXmlExporter putCharRef2Hex(char ch) throws IOException{
304 if(ch > 0xff) return putCharRef4Hex(ch);
306 char hex3 = HEXCHAR_TABLE[(ch >> 4) & 0x000f];
307 char hex4 = HEXCHAR_TABLE[(ch ) & 0x000f];
309 this.appendable.append("&#x");
310 this.appendable.append(hex3);
311 this.appendable.append(hex4);
312 this.appendable.append(';');
318 * 指定された文字を16進4桁の文字参照形式で出力する。
319 * UCS4に伴うサロゲートペアは未サポート
322 * @throws IOException 出力エラー
324 public BasicXmlExporter putCharRef4Hex(char ch) throws IOException{
325 char hex1 = HEXCHAR_TABLE[(ch >> 12) & 0x000f];
326 char hex2 = HEXCHAR_TABLE[(ch >> 8) & 0x000f];
327 char hex3 = HEXCHAR_TABLE[(ch >> 4) & 0x000f];
328 char hex4 = HEXCHAR_TABLE[(ch ) & 0x000f];
330 this.appendable.append("&#x");
331 this.appendable.append(hex1);
332 this.appendable.append(hex2);
333 this.appendable.append(hex3);
334 this.appendable.append(hex4);
335 this.appendable.append(';');
341 * 要素の中身および属性値中身を出力する。
342 * 必要に応じてXML定義済み実体文字が割り振られた文字、
343 * コントロールコード、および非BasicLatin文字がエスケープされる。
346 * @throws IOException 出力エラー
348 public BasicXmlExporter putContent(CharSequence content)
350 int length = content.length();
352 for(int pos = 0; pos < length; pos++){
353 char ch = content.charAt(pos);
354 if(Character.isISOControl(ch)){
356 }else if( ! isBasicLatin(ch) && isBasicLatinOnlyOut()){
360 case '&': this.appendable.append("&"); break;
361 case '<': this.appendable.append("<"); break;
362 case '>': this.appendable.append(">"); break;
363 case '"': this.appendable.append("""); break;
364 case '\'': this.appendable.append("'"); break;
365 case '\u00a5': this.appendable.append('\u005c\u005c'); break;
366 default: this.appendable.append(ch); break;
376 * @param attrName 属性名
377 * @param content 属性内容
379 * @throws IOException 出力エラー
381 public BasicXmlExporter putAttr(CharSequence attrName,
382 CharSequence content)
384 put(attrName).put('=').put('"').putContent(content).put('"');
390 * @param attrName 属性名
393 * @throws IOException 出力エラー
395 public BasicXmlExporter putIntAttr(CharSequence attrName,
398 put(attrName).put('=').put('"').put(iVal).put('"');
404 * @param attrName 属性名
407 * @throws IOException 出力エラー
409 public BasicXmlExporter putFloatAttr(CharSequence attrName,
412 put(attrName).put('=').put('"').put(fVal).put('"');
419 * あらかじめ指定された改行文字が出力される。
421 * 及び非BasicLatin文字はそのまま出力される。
422 * 連続するハイフン(-)記号間には強制的にスペースが挿入される。
423 * @param comment コメント内容
425 * @throws IOException 出力エラー
427 public BasicXmlExporter putCommentContent(CharSequence comment)
429 int length = comment.length();
432 for(int pos = 0; pos < length; pos++){
433 char ch = comment.charAt(pos);
439 if(prev == '-' && ch == '-') put(' ');
450 * あらかじめ指定された改行文字が出力される。
452 * 及び非BasicLatin文字はそのまま出力される。
453 * 連続するハイフン(-)記号間には強制的にスペースが挿入される。
454 * @param comment コメント内容
456 * @throws IOException 出力エラー
458 public BasicXmlExporter putLineComment(CharSequence comment)
460 put("<!--").put(' ');
461 putCommentContent(comment);
469 * あらかじめ指定された改行文字が出力される。
471 * 及び非BasicLatin文字はそのまま出力される。
472 * 連続するハイフン(-)記号間には強制的にスペースが挿入される。
473 * @param comment コメント内容
475 * @throws IOException 出力エラー
477 public BasicXmlExporter putBlockComment(CharSequence comment)
481 putCommentContent(comment);
483 int commentLength = comment.length();
484 if(commentLength > 0){
485 char lastCh = comment.charAt(commentLength - 1);
486 if(lastCh != '\n') ln();