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 CH_SP = '\u0020'; //
34 private static final char CH_YEN = '\u00a5'; // ¥
35 private static final char CH_BSLASH = '\u005c\u005c'; // \
37 private static final char[] HEXCHAR_TABLE = {
38 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
39 'A', 'B', 'C', 'D', 'E', 'F',
42 private static final String COMM_START = "<!--";
43 private static final String COMM_END = "-->";
45 private static final int MASK_BIT8 = 0x000f;
46 private static final int MASK_BIT16 = 0x00ff;
49 assert HEXCHAR_TABLE.length == 16;
53 private final Appendable appendable;
55 private String newline = LF;
56 private String indentUnit = DEFAULT_INDENT_UNIT;
58 private int indentNest = 0;
59 private boolean basicLatinOnlyOut = true;
64 * 文字エンコーディングはUTF-8が用いられる。
65 * @param stream 出力ストリーム
67 public BasicXmlExporter(OutputStream stream){
68 this(stream, CS_UTF8);
74 * @param stream 出力ストリーム
75 * @param charSet 文字エンコーディング指定
77 public BasicXmlExporter(OutputStream stream, Charset charSet){
80 new OutputStreamWriter(stream, charSet)
88 * @param appendable 文字列出力
90 public BasicXmlExporter(Appendable appendable){
92 this.appendable = appendable;
97 * ASCIIコード相当(UCS:Basic-Latin)の文字か否か判定する。
99 * @return Basic-Latin文字ならtrue
101 public static boolean isBasicLatin(char ch){
102 Character.UnicodeBlock block = Character.UnicodeBlock.of(ch);
103 if(block == Character.UnicodeBlock.BASIC_LATIN) return true;
109 * @param newLine 改行文字列
110 * @throws NullPointerException 引数がnull
112 public void setNewLine(String newLine) throws NullPointerException{
113 if(newLine == null) throw new NullPointerException();
114 this.newline = newLine;
119 * BasicLatin文字だけで出力するか設定する。
120 * BasicLatin以外の文字(≒日本語)をそのまま出力するか
123 * @param bool BasicLatin文字だけで出力するならtrue
125 public void setBasicLatinOnlyOut(boolean bool){
126 this.basicLatinOnlyOut = bool;
131 * BasicLatin文字だけを出力する状態か判定する。
133 * @return BasicLatin文字だけで出力するならtrue
135 public boolean isBasicLatinOnlyOut(){
136 return this.basicLatinOnlyOut;
141 * デフォルトではLF(0x0a)\nが用いられる。
142 * @param seq 改行文字列。nullは空文字列""と解釈される。
144 public void setNewLine(CharSequence seq){
145 if(seq == null) this.newline = "";
146 else this.newline = seq.toString();
153 * @param seq インデント単位文字列。nullは空文字列""と解釈される。
155 public void setIndentUnit(CharSequence seq){
156 if(seq == null) this.indentUnit = "";
157 else this.indentUnit = seq.toString();
162 * @throws IOException 出力エラー
164 public void flush() throws IOException{
165 if(this.appendable instanceof Flushable){
166 ((Flushable)this.appendable).flush();
173 * @throws IOException 出力エラー
175 public void close() throws IOException{
176 if(this.appendable instanceof Closeable){
177 ((Closeable)this.appendable).close();
186 * @throws IOException 出力エラー
188 public BasicXmlExporter put(char ch) throws IOException{
189 this.appendable.append(ch);
197 * @throws IOException 出力エラー
199 public BasicXmlExporter put(CharSequence seq) throws IOException{
200 this.appendable.append(seq);
208 * @throws IOException 出力エラー
209 * @see java.lang.Integer#toString(int)
211 public BasicXmlExporter put(int iVal) throws IOException{
212 String value = DatatypeConverter.printInt(iVal);
213 this.appendable.append(value);
221 * @throws IOException 出力エラー
222 * @see java.lang.Float#toString(float)
224 public BasicXmlExporter put(float fVal) throws IOException{
225 String value = DatatypeConverter.printFloat(fVal);
226 this.appendable.append(value);
233 * @throws IOException 出力エラー
235 public BasicXmlExporter ln() throws IOException{
236 this.appendable.append(this.newline);
242 * @param count 改行回数。0以下の場合は何も出力しない。
244 * @throws IOException 出力エラー
246 public BasicXmlExporter ln(int count) throws IOException{
247 for(int ct = 1; ct <= count; ct++){
248 this.appendable.append(this.newline);
256 * @throws IOException 出力エラー
258 public BasicXmlExporter sp() throws IOException{
259 this.appendable.append(CH_SP);
265 * @param count 空白回数。0以下の場合は何も出力しない。
267 * @throws IOException 出力エラー
269 public BasicXmlExporter sp(int count) throws IOException{
270 for(int ct = 1; ct <= count; ct++){
271 this.appendable.append(CH_SP);
278 * インデント単位文字列をネストレベル回数分出力する。
280 * @throws IOException 出力エラー
282 public BasicXmlExporter ind() throws IOException{
283 for(int ct = 1; ct <= this.indentNest; ct++){
284 put(this.indentUnit);
292 public void pushNest(){
299 * インデントレベル0の状態をさらに上げようとした場合、何も起こらない。
301 public void popNest(){
303 if(this.indentNest < 0) this.indentNest = 0;
308 * 指定された文字を16進2桁の文字参照形式で出力する。
309 * 2桁で出力できない場合は4桁で出力する。
312 * @throws IOException 出力エラー
314 public BasicXmlExporter putCharRef2Hex(char ch) throws IOException{
315 if(ch > MASK_BIT16) return putCharRef4Hex(ch);
317 char hex3 = HEXCHAR_TABLE[(ch >> 4) & MASK_BIT8];
318 char hex4 = HEXCHAR_TABLE[(ch >> 0) & MASK_BIT8];
320 put("&#x").put(hex3).put(hex4).put(';');
326 * 指定された文字を16進4桁の文字参照形式で出力する。
327 * UCS4に伴うサロゲートペアは未サポート
330 * @throws IOException 出力エラー
332 public BasicXmlExporter putCharRef4Hex(char ch) throws IOException{
333 char hex1 = HEXCHAR_TABLE[(ch >> 12) & MASK_BIT8];
334 char hex2 = HEXCHAR_TABLE[(ch >> 8) & MASK_BIT8];
335 char hex3 = HEXCHAR_TABLE[(ch >> 4) & MASK_BIT8];
336 char hex4 = HEXCHAR_TABLE[(ch >> 0) & MASK_BIT8];
338 put("&#x").put(hex1).put(hex2).put(hex3).put(hex4).put(';');
344 * 要素の中身および属性値中身を出力する。
345 * <p>必要に応じてXML定義済み実体文字が割り振られた文字、
346 * コントロールコード、および非BasicLatin文字がエスケープされる。
347 * <p>半角通貨記号U+00A5はバックスラッシュU+005Cに置換される。
350 * @throws IOException 出力エラー
352 public BasicXmlExporter putContent(CharSequence content)
354 int length = content.length();
357 for(int pos = 0; pos < length; pos++){
358 char ch = content.charAt(pos);
360 if(Character.isISOControl(ch)){
362 }else if( ! isBasicLatin(ch) && isBasicLatinOnlyOut()){
364 }else if(ch == CH_SP){
370 }else if(Character.isSpaceChar(ch)){
373 }else if(ch == CH_YEN){
377 case '&': put("&"); break;
378 case '<': put("<"); break;
379 case '>': put(">"); break;
380 case '"': put("""); break;
381 case '\'': put("'"); break;
382 default: put(ch); break;
394 * @param attrName 属性名
395 * @param content 属性内容
397 * @throws IOException 出力エラー
399 public BasicXmlExporter putAttr(CharSequence attrName,
400 CharSequence content)
402 put(attrName).put('=').put('"').putContent(content).put('"');
408 * @param attrName 属性名
411 * @throws IOException 出力エラー
413 public BasicXmlExporter putIntAttr(CharSequence attrName,
416 put(attrName).put('=').put('"').put(iVal).put('"');
422 * @param attrName 属性名
425 * @throws IOException 出力エラー
427 public BasicXmlExporter putFloatAttr(CharSequence attrName,
430 put(attrName).put('=').put('"').put(fVal).put('"');
436 * <p>コメント中の'\n'記号出現に伴い、
437 * あらかじめ指定された改行文字が出力される。
438 * <p>コメント中の'\n'以外のコントロールコードは
439 * Control Pictures(U+2400〜)で代替される。
440 * <p>それ以外の非BasicLatin文字はそのまま出力される。
441 * <p>連続するハイフン(-)記号間には強制的にスペースが挿入される。
442 * @param comment コメント内容
444 * @throws IOException 出力エラー
446 public BasicXmlExporter putCommentContent(CharSequence comment)
448 int length = comment.length();
451 for(int pos = 0; pos < length; pos++){
452 char ch = comment.charAt(pos);
456 }else if('\u0000' <= ch && ch <= '\u001f'){
457 put((char)('\u2400' + ch));
458 }else if(ch == '\u007f'){
460 }else if(prev == '-' && ch == '-'){
474 * コメント内部の頭及び末尾に空白が1つ挿入される。
475 * @param comment コメント内容
477 * @throws IOException 出力エラー
479 public BasicXmlExporter putLineComment(CharSequence comment)
481 put(COMM_START).sp();
482 putCommentContent(comment);
489 * <p>コメント内部の頭の前に改行が出力される。
490 * <p>コメント内部の末尾が改行でない場合、改行が挿入される。
491 * <p>ブロックコメント末尾は改行で終わる。
493 * @param comment コメント内容
495 * @throws IOException 出力エラー
497 public BasicXmlExporter putBlockComment(CharSequence comment)
499 put(COMM_START).ln();
501 putCommentContent(comment);
503 int commentLength = comment.length();
504 if(commentLength > 0){
505 char lastCh = comment.charAt(commentLength - 1);