2 * abstract xml exporter
4 * License : The MIT License
5 * Copyright(c) 2013 MikuToga Partners
8 package jp.sourceforge.mikutoga.xml;
10 import java.io.IOException;
11 import java.util.regex.Matcher;
12 import java.util.regex.Pattern;
13 import javax.xml.bind.DatatypeConverter;
19 abstract class AbstractXmlExporter implements XmlExporter{
22 private static final String DEF_NL = "\n"; // 0x0a(LF)
24 private static final String DEF_INDENT_UNIT = "\u0020\u0020"; // ␣␣
26 private static final char CH_SP = '\u0020'; // ␣
27 private static final char CH_YEN = '\u00a5'; // ¥
28 private static final char CH_BSLASH = (char)0x005c; // \
29 private static final char CH_DQ = '\u0022'; // "
30 private static final char CH_SQ = (char)0x0027; // '
31 private static final char CH_EQ = '='; // =
32 private static final char CH_LT = '<';
33 private static final char CH_GT = '>';
35 private static final String COMM_START = "<!--";
36 private static final String COMM_END = "-->";
38 private static final Pattern NUM_FUZZY =
39 Pattern.compile("([^.]*\\.[0-9][0-9]*?)0+");
41 private static final String REF_HEX = "&#x";
42 private static final int HEX_EXP = 4; // 2 ** 4 == 16
43 private static final int MASK_1HEX = (1 << HEX_EXP) - 1; // 0b00001111
44 private static final int MAX_OCTET = (1 << Byte.SIZE) - 1; // 0xff
45 private static final char[] HEXCHAR_TABLE = {
46 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
47 'A', 'B', 'C', 'D', 'E', 'F',
51 assert HEX_EXP * 2 == Byte.SIZE;
52 assert HEXCHAR_TABLE.length == (1 << HEX_EXP);
56 private boolean basicLatinOnlyOut = true;
57 private String newline = DEF_NL;
58 private String indentUnit = DEF_INDENT_UNIT;
59 private int indentNest = 0;
65 protected AbstractXmlExporter(){
72 * ASCIIコード相当(UCS:Basic-Latin)の文字か否か判定する。
73 * <p>※ Basic-Latinには各種制御文字も含まれる。
75 * @return Basic-Latin文字ならtrue
76 * <a href="http://www.unicode.org/charts/PDF/U0000.pdf">
77 * Unicode 6.2 Controls and Basic Latin
80 protected static boolean isBasicLatin(char ch){
81 if('\u0000' <= ch && ch <= '\u007f'){
89 * <p>DatatypeConverterにおけるJDK1.6系と1.7系の仕様変更を吸収する。
90 * <p>0.001fは"0.0010"ではなく"0.001"と出力される。
93 * @return 冗長桁が抑止された実数表記
94 * @see javax.xml.bind.DatatypeConverter
96 protected static String chopFuzzyZero(String numTxt){
99 Matcher matcher = NUM_FUZZY.matcher(numTxt);
100 if(matcher.matches()){
101 result = matcher.group(1);
112 * @throws IOException {@inheritDoc}
115 public abstract void flush() throws IOException;
119 * @throws IOException {@inheritDoc}
122 public abstract void close() throws IOException;
126 * @param ch {@inheritDoc}
127 * @return {@inheritDoc}
128 * @throws IOException {@inheritDoc}
131 public abstract XmlExporter putRawCh(char ch)
136 * @param seq {@inheritDoc}
137 * @return {@inheritDoc}
138 * @throws IOException {@inheritDoc}
141 public abstract XmlExporter putRawText(CharSequence seq)
147 * @return {@inheritDoc}
148 * @throws IOException {@inheritDoc}
151 public XmlExporter sp() throws IOException{
158 * @param count {@inheritDoc}
159 * @return {@inheritDoc}
160 * @throws IOException {@inheritDoc}
163 public XmlExporter sp(int count) throws IOException{
164 for(int ct = 1; ct <= count; ct++){
172 * @return {@inheritDoc}
175 public String getNewLine(){
181 * @param newLine {@inheritDoc}
182 * @throws NullPointerException {@inheritDoc}
185 public void setNewLine(String newLine) throws NullPointerException{
186 if(newLine == null) throw new NullPointerException();
187 this.newline = newLine;
193 * @return {@inheritDoc}
194 * @throws IOException {@inheritDoc}
197 public XmlExporter ln() throws IOException{
198 putRawText(getNewLine());
204 * @param count {@inheritDoc}
205 * @return {@inheritDoc}
206 * @throws IOException {@inheritDoc}
209 public XmlExporter ln(int count) throws IOException{
210 for(int ct = 1; ct <= count; ct++){
218 * @return {@inheritDoc}
221 public String getIndentUnit(){
222 return this.indentUnit;
227 * @param indUnit {@inheritDoc}
228 * @throws NullPointerException {@inheritDoc}
231 public void setIndentUnit(String indUnit) throws NullPointerException{
232 if(indUnit == null) throw new NullPointerException();
233 this.indentUnit = indUnit;
241 public void pushNest(){
250 public void popNest(){
252 if(this.indentNest < 0) this.indentNest = 0;
258 * @return {@inheritDoc}
261 public int getIndentLevel(){
262 return this.indentNest;
267 * @return {@inheritDoc}
268 * @throws IOException {@inheritDoc}
271 public XmlExporter ind() throws IOException{
272 int level = getIndentLevel();
273 for(int ct = 1; ct <= level; ct++){
274 putRawText(getIndentUnit());
281 * @return {@inheritDoc}
284 public boolean isBasicLatinOnlyOut(){
285 return this.basicLatinOnlyOut;
290 * @param bool {@inheritDoc}
293 public void setBasicLatinOnlyOut(boolean bool){
294 this.basicLatinOnlyOut = bool;
300 * @param ch {@inheritDoc}
301 * @return {@inheritDoc}
302 * @throws IOException {@inheritDoc}
305 public XmlExporter putCharRef2Hex(char ch) throws IOException{
306 if(ch > MAX_OCTET) return putCharRef4Hex(ch);
308 int ibits = ch; // 常に正なので符号拡張なし
310 int idx4 = ibits & MASK_1HEX;
312 int idx3 = ibits & MASK_1HEX;
314 char hex3 = HEXCHAR_TABLE[idx3];
315 char hex4 = HEXCHAR_TABLE[idx4];
317 putRawText(REF_HEX).putRawCh(hex3).putRawCh(hex4)
325 * @param ch {@inheritDoc}
326 * @return {@inheritDoc}
327 * @throws IOException {@inheritDoc}
330 public XmlExporter putCharRef4Hex(char ch) throws IOException{
331 int ibits = ch; // 常に正なので符号拡張なし
333 int idx4 = ibits & MASK_1HEX;
335 int idx3 = ibits & MASK_1HEX;
337 int idx2 = ibits & MASK_1HEX;
339 int idx1 = ibits & MASK_1HEX;
341 char hex1 = HEXCHAR_TABLE[idx1];
342 char hex2 = HEXCHAR_TABLE[idx2];
343 char hex3 = HEXCHAR_TABLE[idx3];
344 char hex4 = HEXCHAR_TABLE[idx4];
346 putRawText(REF_HEX).putRawCh(hex1).putRawCh(hex2)
347 .putRawCh(hex3).putRawCh(hex4)
355 * @param ch {@inheritDoc}
356 * @return {@inheritDoc}
357 * @throws IOException {@inheritDoc}
360 public XmlExporter putCh(char ch) throws IOException{
361 if(Character.isISOControl(ch)){
368 case '&': escTxt = "&"; break;
369 case CH_LT: escTxt = "<"; break;
370 case CH_GT: escTxt = ">"; break;
371 case CH_DQ: escTxt = """; break;
372 case CH_SQ: escTxt = "'"; break;
373 default: escTxt = null; break;
387 * @param content {@inheritDoc}
388 * @return {@inheritDoc}
389 * @throws IOException {@inheritDoc}
392 public XmlExporter putContent(CharSequence content)
394 int length = content.length();
397 for(int pos = 0; pos < length; pos++){
398 char ch = content.charAt(pos);
400 if( isBasicLatinOnlyOut() && ! isBasicLatin(ch) ){
402 }else if(ch == CH_YEN){
404 }else if(Character.isSpaceChar(ch)){
405 if(ch == CH_SP && prev != CH_SP){
422 * @param comment {@inheritDoc}
423 * @return {@inheritDoc}
424 * @throws IOException {@inheritDoc}
427 public XmlExporter putCommentContent(CharSequence comment)
429 int length = comment.length();
432 for(int pos = 0; pos < length; pos++){
433 char ch = comment.charAt(pos);
437 }else if('\u0000' <= ch && ch <= '\u001f'){
438 putRawCh((char)('\u2400' + ch));
439 }else if(ch == '\u007f'){
441 }else if(prev == '-' && ch == '-'){
455 * @param comment {@inheritDoc}
456 * @return {@inheritDoc}
457 * @throws IOException {@inheritDoc}
460 public XmlExporter putLineComment(CharSequence comment)
462 putRawText(COMM_START).sp();
463 putCommentContent(comment);
464 sp().putRawText(COMM_END);
470 * @param comment {@inheritDoc}
471 * @return {@inheritDoc}
472 * @throws IOException {@inheritDoc}
475 public XmlExporter putBlockComment(CharSequence comment)
477 putRawText(COMM_START).ln();
479 putCommentContent(comment);
481 int commentLength = comment.length();
482 if(commentLength > 0){
483 char lastCh = comment.charAt(commentLength - 1);
489 putRawText(COMM_END).ln();
496 * @param tagName {@inheritDoc}
497 * @return {@inheritDoc}
498 * @throws IOException {@inheritDoc}
501 public XmlExporter putOpenSTag(CharSequence tagName)
510 * @return {@inheritDoc}
511 * @throws IOException {@inheritDoc}
514 public XmlExporter putCloseSTag()
522 * @param tagName {@inheritDoc}
523 * @return {@inheritDoc}
524 * @throws IOException {@inheritDoc}
527 public XmlExporter putSimpleSTag(CharSequence tagName)
537 * @param tagName {@inheritDoc}
538 * @return {@inheritDoc}
539 * @throws IOException {@inheritDoc}
542 public XmlExporter putETag(CharSequence tagName)
552 * @param tagName {@inheritDoc}
553 * @return {@inheritDoc}
554 * @throws IOException {@inheritDoc}
557 public XmlExporter putSimpleEmpty(CharSequence tagName)
560 putRawText(tagName).sp();
567 * @return {@inheritDoc}
568 * @throws IOException {@inheritDoc}
571 public XmlExporter putCloseEmpty()
579 * @param iVal {@inheritDoc}
580 * @return {@inheritDoc}
581 * @throws IOException {@inheritDoc}
584 public XmlExporter putXsdInt(int iVal) throws IOException{
585 String value = DatatypeConverter.printInt(iVal);
592 * @param fVal {@inheritDoc}
593 * @return {@inheritDoc}
594 * @throws IOException {@inheritDoc}
597 public XmlExporter putXsdFloat(float fVal) throws IOException{
598 String value = DatatypeConverter.printFloat(fVal);
599 value = chopFuzzyZero(value);
606 * @param attrName {@inheritDoc}
607 * @param iVal {@inheritDoc}
608 * @return {@inheritDoc}
609 * @throws IOException {@inheritDoc}
612 public XmlExporter putIntAttr(CharSequence attrName,
615 putRawText(attrName).putRawCh(CH_EQ);
626 * @param attrName {@inheritDoc}
627 * @param fVal {@inheritDoc}
628 * @return {@inheritDoc}
629 * @throws IOException {@inheritDoc}
632 public XmlExporter putFloatAttr(CharSequence attrName,
635 putRawText(attrName).putRawCh(CH_EQ);
646 * @param attrName {@inheritDoc}
647 * @param content {@inheritDoc}
648 * @return {@inheritDoc}
649 * @throws IOException {@inheritDoc}
652 public XmlExporter putAttr(CharSequence attrName,
653 CharSequence content)
655 putRawText(attrName).putRawCh(CH_EQ);